Bryan Irace
July 16, 2014 4:44 pm
Building Adaptive Apps with UIKit
Size classes
Use size classes instead of interface orientation or device idioms. Size classes have both horizontal and vertical components.
Devices and orientations map to size classes in the following way:
iPad Portrait Horizontal = Regular Vertical = Regular Landscape Horizontal = Regular Vertical = Regular
iPhone Portrait Horizontal = Compact Vertical = Regular Landscape Horizontal = Compact Vertical = Compact
UITraitCollection
Consists of:
horizontalSizeClass
verticalSizeClass
userInterfaceIdiom
displayScale
Unspecified values are ignored when checking if one trait collection "contains" another (containsTraitsInCollection:
on UITraitCollection
)
Trait collections can be "combined" - traitCollectionWithTraitsFromCollections:
- Unspecified always loses
- Last value wins if two different values (e.g. compact + regular = regular)
Trait environment protocol
Trait environments provide a trait collection object
Conformed to by:
UIScreen
UIWindow
UIViewController
UIView
UIPresentationController
Comprise a hierarchy; view inherits collections from controller, inherits from window, inherits from screen, etc.
Provides a hook for reacting to changing traits
traitCollectionDidChange:
willTransitionToTraitCollection:withTransitionCoordinator:
(view controller only, not on all trait environments) Can use the coordinator to animate alongside the transition animation Changes to Auto Layout constraints should be done in here, plus callingsetNeedsLayout
* Can also add completion blocks to be run during cleanup
You might get an "unspecified" trait collection value if:
- Your environment is a
UIView
and isn't in the hierarchy - You've created it yourself
Appearance proxies
Appearance proxies can be applied only when certain trait collections match, e.g. apply this appearance only when horizontal size class is "compact"
UIImage
Instead of just providing separate images based on display scale, can also provide separate images based on trait collections now
UIImageAsset
Wraps up all versions of an image, allows you to manually ask for an image matching a specific trait collection
Examples
- A view has different horizontal and vertical padding depending on if compact or regular
- When trait collection changes, invalidate intrinsic content size
In intrinsic content size method, use size class to determine padding value accordingly
A view uses stacked in regular vertical size class and side-by-side in compact vertical size class
- When trait collection changes, remove old constraints and set up new ones based on vertical size class
Overriding inherited trait collections
In a container view controller, we can add our own trait collection to the one that's inherited by the child view controller using setOverrideTraitCollection:forChildViewController
Ex: tricking an iPhone in landscape to have regular horizontal size class instead of compact, which is the default
Rotating from landscape to portrait results in changing horizontal size class from regular to compact
Need to find a "primary view controller", by default the master view controller from the split view controller
- Overridable via
primaryViewControllerForCollapsingSplitViewController:
- Overridable via
Need to find a "secondary view controller", by default the detail view controller from the split view controller
- By default, pushed on a navigation stack on top of the primary view controller
- If, for example, we want the primary view controller only, we can use
splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
- If, for example, we want the primary view controller only, we can use
- By default, pushed on a navigation stack on top of the primary view controller
Rotating from portrait to landscape results in changing horizontal size class from compact to regular
Need to find a "primary view controller", by default the root view controller from the portrait navigation stack
- Overridable via
primaryViewControllerForExpandingSplitViewController
- Overridable via
Need to find a "secondary view controller", by default the rest of the portrait navigation stack
- If, for example, we only have the root in portrait and need a detail view for landscape, we can use
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
- If, for example, we only have the root in portrait and need a detail view for landscape, we can use
Changes to how view controllers are shown
Traditional way that a view controller reaches back to its parent navigation controller in order to push a new controller on the stack is bad coupling
New methods provide a looser coupling:
showViewController:sender:
- In UINavigationController, pushes on stack
- In UISplitViewController, replaces primary view controller (this doesn't make sense to me)
- Without a container, presents modally
showDetailViewController:sender:
- In UISplitViewController, replaces secondary view controller
- If collapsed, forwards to it's primary view controller instead
- E.g. in a navigation controller, pushes on stack
- If collapsed, forwards to it's primary view controller instead
- Without a container, presents modally
- In UISplitViewController, replaces secondary view controller
Interface builder
Can now target both the iPhone and iPad from a single Storyboard or XIB