Tandem Views for any iOS Screen

This tutorial is designed to show you how to use Auto Layout to correctly position two UIViews so they appear stacked in portrait mode or side-by-side in landscape mode on any iPhone screen. They will also be stacked on any iPad screen, regardless of orientation (although I wish there was an iPad landscape size class).

It’s also meant to emphasize keeping layout constraints to a minimum. Auto Layout can get complicated and convoluted in a hurry if you’re not careful. This is only worsened by Xcode’s poor UI, which constantly wants to truncate panes and their content, making it hard to see constraints.

As guiding principles, consider the following:

  • Analyze your layout needs before you start building
  • Look for constraints that will never change – the “absolutes”
  • Use the fewest constraints and the fewest size classes possible

Analyze the layout before building

Ask yourself what will be the best and necessary ways to present content or features to your app’s users. Sketch out what needs to be shown. It’s best if you have a good grasp of all the potential views across screen sizes, screen orientation and recently introduced features like split-screen or slide over views. I found that it was not enough to look at the schematic visual definitions you get when choosing a size class. So I created a template document showing all the possible combinations and definitions for size classes so I could keep it all straight:

You can download this PDF size class chart and either print it out to sketch or scribble on or edit it in Illustrator.

Look for the constraints that will never change, regardless of screen size or orientation.

Working from those and working inward from larger elements to smaller really helps. Taking this example, we can see that the first (yellow) view will always have its top and left edges aligned with the top and left of the Super View. The second (blue) view will always have its bottom and right edges aligned with the bottom and right of the Super View. (The black triangles are just there to indicate the orientation of the views’ content).

Later on we’ll cover the details of setting these constraints. For now, just keep in mind that our aim is to use as few rules as possible to achieve a layout. It’s no fun wading through a ton of constraints to find what you think is the one causing an issue. It’s even less fun to update a layout and suddenly see it go kablooey because of unexpected interdependencies. Any individual constraint can have a number of variables. Additionally, sometimes constraints only apply to one size class, which can lead to further troubleshooting nightmares.

Use as few size classes as possible

In this example, only two size classes will be used. Again, the value of using the size classes template to analyze the screen layout can really help. After I drew all the variations I realized I only needed two size classes for this – the generic wAny hAny and the compact height class wAny hCompact:

In the end, there will be a mere 12 constraints. Only 4 of them will be in the wAny wCompact size class. That will be enough to accommodate 7 different layout modes and any size/resolution screen.

Here’s how the views will appear across a multitude of possibilities:

Getting Started

Start by creating a new project in Xcode. In this case, I named it TandemView. Make sure Devices is set to Universal. Once the project is created, select Main.storyboard from the Project navigator. Its size class should be set to wAny hAny:

With the storyboard in view, open the Utilities pane if it isn’t open already and select the Object Library icon. Type “view” in the Filter field at bottom:

Select the View object and drag it onto the storyboard. Don’t worry about position or size:

To make the View easier to work with, change the background color and the name. In my case, I chose Background > Other… and set the color to a hex value of FFCC33 and the opacity to 62% (this will really help you see if there are any overlap issues). I gave the View a name of FirstView:

Next, add a Label object (using the Filter field makes it easy to find in the Object
Library) to the new view (not its parent view). It’s not important to change the text, but if this sort of thing leaves you feeling incomplete, feel free to do so:

Note: Use Editor > Size to Fit Content (CMD=) to get the Label to show all the text.

Using Auto Layout

Note: Sometimes you will set a constraint on a parent and child item and it will be expressed in reverse, e.g. “top = FirstView.top”. I recommend switching these around so that the child is on the left-hand side of the equation. Semantically and logically this better shows the relationship between the two objects. To do this, click on either item’s pulldown in the Attribute Inspector and select Reverse First and Second Item.

That said, we’re finally ready to start using Auto Layout! The first task is to just get the new sub view centered inside its Super View. To do this, with the Label still selected, select the Align icon () from the bottom right of the Standard Editor. Check the boxes next to Horizontally in Container and Vertically in Container. Set Update Frames to Items of New Constraints and click Add 2 Constraints.

And, voila!

Wait a minute!

What the h*** just happened?

Welcome to Auto Layout my friend. Your new view just got sized down to the proportions of the label. If, like me, you are used to the general sensible nature of XAML or have spent much time with CSS, this will just boggle the mind. But fear not. Take a giant swig of whatever is keeping you sane and soldier on.

Select both the parent view and FirstView (by holding the CMD key down while clicking on each). Then select the TIE-Fighter-looking Pin icon () from the bottom right of the Standard Editor. Choose the following options:

Now you should see this:

If you’re curious to see what this looks like in the simulator, hit CMD-R and have a look. You should see completely yellow screen with the label centered in it. Using CMD with the arrow keys to toggle between portrait and landscape you can see the effects.

Back in Xcode, you may have already noticed there are a couple of issues. Both the horizontal and vertical positions of FirstView are “ambiguous”. The rendering engine in iOS did a fine job of drawing this single view to fit the screen. But it’s really going to help down the road if these issues are dealt with now (and it will make Xcode happy). Left unfixed, these sorts of things have a nasty way of making portions of the layout disappear when updating frames.

Harking back to our first principle of looking for absolutes, we had already determined that this view’s top and left edges will adhere to the Super View‘s top and left, regardless of orientation, screen size or any other factor. Auto Layout offers a couple ways to set this, such as using the margins. In this case, I prefer to align the top and leading edges of the views. To do this, CMD-select both FirstView and its parent again. Select the Align icon () and make the following changes:

Note: Selecting All Frames in Container will head off any further errors, such as the Label being reported out of alignment.

You won’t see any difference in the simulator but the errors should be gone. More importantly, the view is going to be placed correctly even after we make further changes to it.

Now it’s time to alter one of the constraints. We need to change things so that FirstView‘s height is only half the height of the Super View. Under the scene, select FirstView.height = height from the Constraints. (You may need to click on the arrow to the left of the Constraints heading to expand them).

Over on the right-hand side of the editor, in the Utilities pane, the Size Inspector should be visible. Edit the values shown like this:

Note: The Priority value is lowered is that we will be adding another constraint that will override it when a phone is placed in landscape mode, matching the wAny hCompact size class.

The layout should now look like this:

Run the simulator and you should see screens like this:

Obviously, since we’ve thus far only set up constraints in the generic wAny hAny size class the view is going to look strange in landscape mode. Time to fix that. But first, one other edit. Just as with the FirstView.height constraint, we also need to set its width constraint’s priority to 750. Select that constraint and in the Size Inspector change the value.

Switch to wAny hCompact and CMD-select both FirstView and its parent view.

Hey, guess what? We’re back to this…at least in this size class…and when run in the simulator in landscape mode:

Now if that ain’t progress I don’t know what is! But fear not. Auto Layout is just doing what it was told to do. We’re going to make a quick edit to the new width constraint and will be seeing real progress in a moment. Switch back to wAny hAny. This will make it easy to see the two new constraints because they will be faded out. However, they are still selectable and editable. Select FirstView.width = width and in the Size Inspector change the Multiplier value to 0.5. Now if you switch back to wAny hCompact or run the simulator and toggle between portrait and landscape you’ll see that the view is now transforming correctly:

Note: Because the width constraint set under the wAny hCompact size class has a higher Priority it overrides the width constraint set under wAny hAny. Apple, of course, generally sets these priorities at 250, 750 or 1000 – depending on the purpose of the constraint. You can set them at any value and whichever constraint affecting the same parameters has the higher value will take precedence. This is all the more reason to plan ahead and anticipate necessary changes. The fewer the better!

Creating the Second View

You can either follow the initial steps used to create the first view or select it and copy it. If doing the latter, you will need to change its name to SecondView. Also, note that constraints are not copied along with the view (other than for internal items, such as the label). Regardless of whether you choose to start from scratch or copy, there are a few things that need to happen. The view needs a different name, as mentioned (whether changing it from the generic “View” or “FirstView”. It needs a different background color (although be sure to keep the opacity to 62%, so using the color picker is recommended). Also, the label’s value should be changed to “Second View”. Finally, constraints need to be added both in the wAny hAny size class and the wAny hCompact size class. These will be almost the same as those set for FirstView, with two critical differences – SecondView’s
Trailing and Bottom edges will be set to track the right and bottom of the Super View rather than its left and top.

Before adding any constraints, the layout should look more or less like this:

Note: You may see that four issues have been raised in Main.storyboard, related to the ambiguity of SecondView’s height, width and position. These should go away once the constraints are added.

First, ensure that the wAny hAny size class is selected. Select both the parent view and SecondView (by holding the CMD key down while clicking on each). Then select the Pin icon () from the bottom right of the Standard Editor. Choose the following options:

Now you should see this:

Two of the errors went away but of course the view has dutifully filled the entire screen. Additionally, while the horizontal and vertical position are still ambiguous, the layout engine has done its best to place the view. That doesn’t mean it’s okay to leave as is. Even if we didn’t need this view to adhere to the right and bottom edges of the screen, not setting those basic constraints can lead to confusion later.

The next step, obviously, is to select the SecondView.height = height constraint and edit its Priority to 750 and Multiplier to 0.5 in the Attributes Inspector. Don’t forget to also select the SecondView.width = width constraint and set its Priority to 750 as well.

Just to prove the point about about the necessity of setting basic constraints, click anywhere on the storyboard and then click on the Resolve Auto Layout Issues button () at lower right and select Update Frames under All Views in View Controller. On my screen the storyboard now looks like this:

Select both SecondView and the parent view. Then click on the Align Tool () and set the following constraints:

Running the simulator with an iPhone target and toggling between portrait and landscape mode shows that we’re most of the way there. However, in landscape mode SecondView needs to have its width and height constraints overridden in the wAny hCompact size class.

Switch to wAny hCompact and CMD-select both SecondView and its parent view.

Now, select the SecondView.width = width constraint and set the Multiplier to 0.5. (Switching back to wAny hAny will make it easier to see which constraint is which because the constraints applied under other size classes will be faded out. Select the faded out version of the constraint before applying the edit). You can verify that the desired effect has occurred either by switching back to wAny hCompact or, better yet, running the app in the simulator or an actual device. (This can be a good time to switch to other device profiles, such as an iPad model to see how the constraints apply).

On a phone you should be seeing this: