Adaptive Layout – Part 3: Orientation specific layouts

As explained in the Part 1 of this adaptive layout series, the size classes give an approximate idea about the horizontal and vertical dimensions of that object (width and height). They are returned by the traitCollection property of any object that conforms to the UITraitEnvironment protocol, like UIView or UIViewController instances.

The size classes don’t represent absolute dimensional values; instead, they indicate if the width or heigh is rather small (Compact size class) or big (Regular size class) in a relative way, allowing to define layouts specific to each size class combination.

The problem with this method of defining the layouts depending of the size classes is the lack of precision. The same size class combination represent multiple device screens and orientations, and sometimes the same combination is returned both in portrait and landscape orientations (iPad), as explained in Apple’s documentation:

size classes for device screens and orientations

The overall layout often depends on the physical screen size. By applying the Adaptive Layout principles, it is defined in a similar way the Responsive Web design presents the content using CSS media queries. It is constrained by the actual viewport size, which is different in portrait in landscape orientations. It’s thus important to be able to react to the screen rotation and adjust the layout depending on the current orientation.

UIContentContainer protocol

The UIContentContainer protocol was introduced in iOS8 to provide callbacks for the changes in size or trait collection and allow developers to execute custom code in response to these changes.

The UIViewController and UIPresentationController classes conform to this protocol.

The two methods allowing to detect size changes on the horizontal or vertical axis are:

  • -willTransitionToTraitCollection:withTransitionCoordinator:: if the layout doesn’t depend on the actual screen size, but only on the size class, this method can be used to trigger the layout change. It can be used when there is a layout for the Compact and another layout for the Regular size classes.
  • -viewWillTransitionToSize:withTransitionCoordinator:: if the layout depends on absolute sizes (dimensions in points), this method should be used make the layout adjustments for the new size passed in the first argument.

Starting with iOS8, they replace the UIViewController methods -willRotateToInterfaceOrientation:duration: and -didRotateFromInterfaceOrientation:.

The two delegate methods have a specific structure and contain a sequence of other methods to call. The custom layout code in inserted in between these methods:

Orientation specific layouts

I’ve built a simple example to show how to implement an orientation dependent layout and how to transition from one layout to another in response to the device rotation. The sample code is available here for download.

orientation specific layout

Defining the layouts

The content is displayed in a custom view which contains three square subviews.

The subviews are laid out using Auto Layout constraints. Because portrait and landscape orientations require different sets of layout constraints, I’ve separated the creation of these constraints in two methods that return the constraints as NSArray objects:

In a similar way, it is possible to create more specific layouts depending on the absolute value of the bounds size. This can be useful for layouts that depend on the actual points size.

The set of constraints specific to the current layout are applied in the -updateConstraints method after removing the previous constraints:

Determining the orientation is a simple matter of measuring the aspect ratio of the custom view, which is equivalent to comparing its width to the height:

Triggering the layout change

The custom view is added as a subview of the view controller’s main view and is attached to the four edges of the superview.

When the orientation superview changes, the system invokes the -viewWillTransitionToSize:withTransitionCoordinator: method, in which I’ve called the -setNeedsUpdateConstraints method on the custom view to replace the current constraints with those specific to the new orientation:

Conclusion

Performing layout adjustments depending on the device orientation is different since the introduction of size classes and adaptive layout in iOS8. This article explains how to react to changes in the views size during the rotation using the UIContentContainer protocol which replace the deprecated UIViewController methods -willRotateToInterfaceOrientation:duration: and -didRotateFromInterfaceOrientation:.

 

Catalin Rosioru