Bryan Irace

July 16, 2014 4:44 pm

Building Adaptive Apps with UIKit

WWDC Session 216, 2014

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 calling setNeedsLayout * 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:
    • 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:
  • 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
    • 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:

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
    • Without a container, presents modally

Interface builder

Can now target both the iPhone and iPad from a single Storyboard or XIB