Custom views and Auto Layout

Auto Layout is a powerful system that manages how the views are displayed on screen in relation with their content and the other parent and sibling views. This system was introduced by Apple in OS X 10.7 and iOS 6, and seems to be here for the long term.

The constraint based layout supported by this technology allows developers to build user interfaces that easily adapt to multiple screen sizes and orientations; its purpose is to help us design responsive app interfaces by writing as few lines of code as possible, possibly none when all the constraints are defined in Interface Builder.

This article focuses on creating custom views that use Auto Layout to adapt their size and placement depending on their content and the parent view. Basic knowledge about Auto Layout and how constraints work is useful for a good understanding of all the concepts discussed here. Auto Layout is not something easy to wrap your head around, but it’s worth the effort. It will be harder and harder to design user interfaces without it if you want to provide the best user experience.

UIView is often subclassed when we need to alter the behavior or add custom attributes to the default view class. If the custom view is part of a view hierarchy managed using Auto Layout, we usually override some UIView methods to specify how it should behave in this constraint based system.

Enable Auto Layout for custom views

UIView and its subclass instances created in code and added to the view hierarchy using the -addSuview: method don’t support Auto Layout by default. If we try to add constraints to these views, the system throws an exception saying it’s unable to satisfy the constraints because of some NSLayoutResizingMaskConstraints issue.

To enable Auto Layout in a custom view, we have to set the translatesAutoResizingMaskIntoConstraints property to YES and we usually do that in the designated initializer. For views added to the hierarchy in the storyboard, Auto Layout in enabled by default:

auto layout enabled by default in interface builder

Additionally, it’s possible to force a custom view to be used only in a constraint based view hierarchy by returning YES in the overridden method -requiresConstraintBasedLayout.

How views are rendered to the screen

With Auto Layout enabled, views are rendered in a three step process, and we are able to hook in each of these steps to add our custom behavior:

  1. add or update the internal constraints
  2. lay out the view hierarchy
  3. draw the contents

Each step depends on the one before. The first two steps are performed behind the scenes, while the last one does the actual rendering to the screen.

Update the internal constraints

A custom view may define constraints related to its own size or aspect ratio. It may contain subviews which are also laid out using constraints.

We can override the -updateConstraints method where we can add or modify the constraints directly related to the custom view’s content. This method is automatically called in the first rendering step, but we can also trigger it by calling the -setNeedsUpdateConstraint method.

It’s very important to call [super updateConstraints] at the end of the overridden method.

Lay out the subview tree

If the custom view contains subviews and we want to control how they are placed in relation to each other or to the parent custom view, we have to override the -layoutSubviews method.

In this method we probably need to know the size and the position of the subviews already present in the view tree as they are dynamically laid out by the system. This is necessary when we want to adjust the location and size of the new subviews depending on the existing ones. Calling [super layoutSubviews] will force the system to set the frames for all the subviews in the hierarchy.

The -layoutSubviews method is invoked automatically by the system after the internal constraints are updated, but it can also be triggered manually by calling the -setNeedsLayout method, in which case it is executed at the next run loop. If we need to execute it immediately, we call the -layoutIfNeeded method.

The layout pass could update or remove some of the internal constraints set by the -updateConstraints method. Setting the constraints and laying out the subview tree is an iterative process, and the -layoutSubviews method could trigger the -updateConstraints, which, in turn, produces another layout pass.

Draw the contents

If we need to perform drawing inside the custom view using Core Graphics, we do that by overriding the -drawRect: method.

This method is automatically executed after the custom view and its subviews final frames are known, but can also be triggered manually by calling the -setNeedsDisplay method and it is executed at the next run loop.

Custom view with Auto Layout by example

Enough with the theory, let’s write some code!

In the project available for download here you will find a simple implementation of a custom view containing a set of subviews which are positioned using Auto Layout.

The goal is to display several text boxes of different heights in a two column layout by maximizing the screen occupation:

text boxes in portrait mode

Setting the local constraints

Each custom view subview contains an UILabel which displays a multi-line attributed string with random font and size. It’s nothing more than a box containing some text.

The UILabel is pined to the four edges of the subview.

Laying out the subviews

In the text box class, we set the preferredMaxWidth property of the UILabel to help the system calculate the intrinsic content size of the multi-line UILabel. Because the preferred width depends on the container bounds, first we call [super layoutSubviews] to make sure the label’s parent view width was priorly determined by the system depending on its constraints.

The text box height adapts automatically to the text length, the font and the font size. Each box background is a random color to better visualize how the subviews are placed in relation to each other.

The text box container class, which is the main custom view, is split in two columns of equal widths.

The first two text boxes are pinned to the top of the custom view and have same width. We add all the necessary constraints with only one command using the visual format language. The next boxes are placed either in the first or the second column, depending on the level of their bottom edge, to optimize the screen occupation.

Of course, the text boxes width adapts to the screen orientation:

text boxes in portrait mode

Conclusion

The important thing to keep in mind after reading this article is that we can interfere at several levels in the view layout process by overriding methods like -updateConstraints, -layoutSubviews, -intrinsicContentSize and other that are described in the Apple documentation.

 

Catalin Rosioru