Update 19th November 2014: Updated for Xcode 6.1.
The introduction of Adaptive Layout in iOS 8 is a huge paradigm shift for iOS app designers. When designing your app, you can now create a single layout, which works on all current iOS 8 devices – without crufty platform-specific code!
This tutorial serves as your introduction to Adaptive Layout. You’ll learn about universal storyboards, size classes, layout and font customizations and the ultra-useful Preview Assistant Editor.
You’ll create the user interface for a simple weather app – and you’ll build it completely from scratch. If you’re not a fan of Auto Layout, don’t worry; the first part of this tutorial provides you with a gentle step-by-step approach to build an interface using Auto Layout. You’ll be amazed at how much you can accomplish without writing a single line of code!
Universal Storyboards are the first step on your journey towards Adaptive Layout. The same storyboard can now be used for both iPads and iPhones running iOS 8. There’s no need to keep per-device storyboards in sync with each other – a monotonous process which can be fraught with error.
Open Xcode and select Create a new Xcode project:
Select iOS\Application\Single View Application, then click Next:
Set Product Name to AdaptiveWeather, Language to Swift and ensure that Devices is set to Universal:
Once the project opens, take a look at the Project Inspector and you’ll see just a single storyboard file:
Main.storyboard is the single storyboard for all devices, no matter their screen size. Open the storyboard and you’ll see that it contains a single view controller, but of an unfamiliar size:
That’s right – it’s now a rough square! Storyboards in previous versions of Xcode had to match the screen size of the target device. This clearly isn’t possible with the “one storyboard to rule them all” approach, so the storyboard is given an abstract size instead.
The Use Size Classes option, found in the File Inspector, enables this new format for your project; select the storyboard, open the File Inspector and you’ll see the checkbox option as shown below:
This option is enabled by default for all new iOS 8 projects. You can turn this option on yourself when upgrading your old projects to use the new storyboards.
Setting Up Your Storyboard
To start, open Main.storyboard and drag an Image View from the Object Library onto the view controller canvas. In the Size Inspector, set the X position to 150 and the Y position to 20. Set the Width to 300 and the Height to 265.
Next, drag a View from the Object Library and place it below the image view. In the Size Inspector, set the X position to 150 and the Y position to 315. Set the Width to 300 and the Height to 265.
Select the view you just added, open the Identity Inspector and enter TextContainer in the Label field. Note that the Document pane might be collapsed – if so press the Show button to reveal it. This gives the view a name and makes it easier to see in the Document Inspector. This view will eventually hold the city and temperature labels of your app.
It’s often hard to see views after dragging them in from the Object Library as their default background color is white, the same as the view controller’s view. To fix this, select the view controller’s view, open the Attributes Inspector and set its background color to Red: 74, Green: 171, Blue: 247.
Next, select the TextContainer view and set its background color to Red: 55, Green: 128, Blue: 186.
Your view controller should now look like the screenshot below:
These two views are the only direct descendants of the view controller’s view; your task now is to give them some layout constraints.
Adding Layout Constraints
Select the image view and press the Align button in the auto layout toolbar. Check the Horizontal Center in Container, ensuring the value is set to 0, and click Add 1 Constraint.
Then press the Pin button and add a top spacing to nearest neighbor constraint of 0, as so:
The constraints you added above ensure that the image view has a fixed size margin from the top and that it is centered left to right. Now you need to configure the space between the image view and the text container view. Ctrl-drag from the image view down to the container view, like so:
This displays the constraint context menu again. Select Vertical Spacing:
This constraint determines the amount of vertical space between the bottom of the image view and the top of the TextContainer view.
The Size Inspector looks a bit different in Xcode 6. Select your image view and open the Size Inspector to see how it looks now:
You’ll see the three constraints you just added to your layout; you can easily configure each constraint from within the size inspector. Press the Edit button in the Bottom Space To: TextContainer constraint; you’ll be presented with a dialog to configure the constraint properties. Set Constant equal to 20:
Click away from the dialog to close it.
You’ve already configured your TextContainer view to have a gap of 20 points from the bottom edge of the image view, but you also need to add constraints to the other three sides of the view.
Select your TextContainer view then click the Pin icon in the bottom bar to show the Add New Constraints dialog. In the Spacing to nearest neighbor section, set the left, right and bottom spacing to 0. Ensure that Constrain to margins is unchecked; this removes padding from around your view.
For reference, the dialog should now look like the following:
Click Add 3 constraints to add the new constraints to your view. This pins the text container view to the left, right and bottom edges of the view controller’s view.
Your storyboard should now look like the screenshot below:
You’ll notice a few orange constraints on your view; this indicates there are issues with these constraints that need your attention. It’s possible to have the storyboard automatically update the frames of the contained views to satisfy these constraints, but if you do this right now your image view will shrink to zero size.
That’s because your image view doesn’t yet have any content – which means it has an intrinsic height and width of zero. Auto Layout relies on a view’s intrinsic size to determine its width and height if no physical width or height constraints are supplied.
In the Project Navigator, open Images.xcassets, then click on the + sign at the bottom of the left panel and select New Image Set from the popup menu:
Double-click the title Image in the top-left corner of the detail view and rename Image to cloud, as shown below:
Your new image set is simply a collection of image files, where each file has a purpose for a particular scenario. For example, an image set may have non-Retina, Retina and Retina HD versions of the same image. Since asset libraries are well integrated with UIKit, you refer to the entire image set by name and let iOS choose the correct file at runtime.
Note: If you’ve used an asset library in the past then you’ll notice something strange here – what’s this 3x all about?
This new display scale is known as Retina HD and is found on the iPhone 6 Plus. This means that in each direction, there are actually 3 pixels making up one point – i.e. there are 9 times more pixels in a 3x image than a 1x image!
Your cloud image set is currently empty. Download cloud_images.zip and unzip it. Inside you’ll find three files. Drag cloud_small.png from the finder to the 1x box in the cloud image set like so:
Since the cloud image is white with a transparent background, you won’t be able to see it easily in the asset library. However, you can preview the image in QuickLook. Select the 1x box and press space:
Now drag email@example.com to the 2x box, and firstname.lastname@example.org to the 3x box. Again, you won’t be able to clearly see the images, but you can check using QuickLook.
You can now use your image set to populate your image view. Head back over to Main.storyboard and select your image view. Switch to the Attributes Inspector, type the name cloud into the Image field, and select Aspect Fit in the View\Mode drop down list, like so:
Your storyboard now should look like the following:
There’s your image – but as you can tell from the orange constraints, you still have a bit of work to do. First select the view controller’s view:
Then select the Resolve Auto Layout Issues icon in the bottom bar. Finally, select All Views\Update Frames from the dialog as shown:
The view controller re-arranges the views to automatically resolve the unsatisfied constraints, which should leave your storyboard looking like this:
The Preview Assistant Editor
Normally you’d now build and run your project on the iPad, iPhone 4s, 5s, 6 and 6 Plus simulators – in both orientations – in order to test this new universal storyboard. This process is laborious at best, but Xcode 6 gives you a much better option with the new Preview Assistant Editor.
Open Main.storyboard then click View\Assistant Editor\Show Assistant Editor. This splits the screen into two panes. Click Automatic in the Jump Bar and choose Preview from the drop down menu. Finally select Main.storyboard:
The assistant editor now displays a preview of the storyboard on the 4-inch iPhone screen, as shown below:
Rotate this preview by clicking on the rotation icon at the bottom of the preview panel alongside the preview name, in this case iPhone 4-inch. The preview will now display in the landscape orientation:
This is a huge improvement over firing up multiple simulators – but wait! There’s more! Click the + icon at the bottom left of the preview editor to reveal the list of available previews:
Select iPhone 5.5-inch and then iPad from this list to add their previews into the mix as shown below:
Notice anything odd about the landscape iPhone preview? That’s right – the cloud image is far too big. To fix this you’ll add a new constraint to the image view.
Head back to the storyboard. Ctrl-drag from the image view to the view controller’s view to create a new constraint. From the context menu, select Equal Heights:
A number of constraints on the storyboard are now colored red. This is because the constraint you just added conflicts with the existing constraints, as it’s not possible for the image view to have the same height as the view controller’s view and maintain the vertical margins you created earlier.
Select the constraint you just added in the Document Outline and open the Attributes Inspector. If the First Item is not set to cloud.Height then select Reverse First and Second Item in the First Item dropdown as shown below:
Next, select the Relation to be Less Than or Equal set Multiplier to 0.40 as shown below:
This means that the cloud image will either be the intrinsic size of the image, or be 40% of the height of the screen, whichever is smaller.
You’ll notice that the preview pane automatically refreshes as soon as you update the constraint, as demonstrated below:
So you can get multiple device previews that also update automatically? Yep, the new Preview Assistant Editor is quite an improvement!
Since this is supposed to be a weather app, you should probably add some labels to show the city name and the current temperature.
Adding Content to the TextContainer
In Main.storyboard drag two Labels from the Object Library onto the TextContainer view, and arrange them roughly as shown below:
Select the topmost label and use the Align and Pin menus to center the label horizontally and add a top spacing to nearest neighbor of 10, as shown below:
Next, select the Attributes Inspector and set Text to Cupertino, Color to White, and the font to Helvetica Neue, Thin with Size of 150.
You’ve probably noticed the text is currently illegible, and this is due to the frame of the label – you’ll resolve this shortly.
Now select the other label, and again use the Align and Pin menus to center it horizontally, and set a bottom space to nearest neighbor of 10. Check that the Size Inspector matches the following:
Use the Attributes Inspector to set the Text to 28C, the color to White, and the font to Helvetica Neue, Thin with a size of 250.
Now you need to resolve the label frame issues mentioned earlier. Select the view controller’s view, click the Resolve Auto Layout Issues button at the bottom of the storyboard, and select All Views\Update Frames. You’ll see the storyboard update:
The labels overlap a little in the storyboard, which isn’t the look you’re going for. However, take a look at the preview in the assistant editor before you fix anything. The iPad version is looking pretty good:
Predictably, the font size is far too big for the iPhone:
You’ll correct these sizing issues in the next section of this Adaptive Layout tutorial.
Universal storyboards are great – but you’ve already discovered that creating one single layout for all displays is a bit of a challenge. However, Adaptive Layout has a few more tools and tricks to solve these issues.
One of the core concepts behind adaptive layout is size classes. A size class is a property applied to any view or view controller that represents the amount of content that can be displayed in a given horizontal or vertical dimension.
Xcode 6 provides two size classes: Regular and Compact. Although they are related to the physical dimensions of a view, they also represent the semantic size of the view.
The following table shows how the size classes apply to the different devices and orientations:
These are the size classes that the device hands down to the app. However, you can override these size classes at any point in the view hierarchy. This can be useful when using a view controller in a container which is significantly smaller than the screen.
Size Classes and You
What does that mean for you and the design of your app? Although your app is aware of size classes, the layout you’ve built is size class agnostic – that is, your layout remains the same for all size classes.
This is an important point when it comes to the design phase of your adaptive layout. You should build a base layout first and then customize each specific size class based on the individual needs of that size class. Don’t treat each of the size classes as a completely separate design. Think of an adaptive layout as a hierarchy, in which you put all of the shared design into the parent and then make any necessary changes in the child size classes.
There’s been almost no mention of configuring layouts for specific devices so far. This is because a core concept of adaptive layout is that size classes are abstracted away from device-specific characteristics. This means that a view that supports adaptive layout could be used in a full-screen view controller as well as in a contained view controller, albeit with different appearances.
This also benefits Apple, as there is room to expand the range and characteristics of their devices without forcing developers and designers to re-engineer their apps.
You’ll use size classes to customize the landscape layout for iPhone since the current layout doesn’t cope well when restricted vertically.
Working with Class Sizes
Click on w Any h Any in the bottom bar in the Interface Builder; you’ll see the size class selector as shown below:
Here you can choose which size class to show in the storyboard by selecting cells within the grid. There are nine possible options: you have three choices for vertical (any, regular or compact) and three for horizontal, resulting in a total of 3×3 = 9 size class combinations.
Note: There is a slight discrepancy in nomenclature at this point. Size classes are always referred to as horizontal and vertical. However, IB uses the terms width and height. There’s an obvious parallel (width = horizontal; height = vertical); just be aware that there are two terms for the same concept.
Your current layout doesn’t work properly for compact heights. To fix this, choose the cells shown below to select the Any Width | Compact Height size class:
You’ll immediately notice two changes in the editor:
- The shape of the canvas changes to represent the new size class
- The bottom bar turns a rather fetching shade of blue. This indicates that you’re now working on a size-class specific layout
In order to change the layout, you’ll need to temporarily change a few constraints. In Auto Layout terminology this is known as installing and uninstalling constraints. A constraint is installed if it is currently active, whereas an uninstalled constraint is not currently active within the current size class.
Click on the image view to select it, and open the Size Inspector. You can see a summary of the constraints which affect this view:
Select the Align Center X to: Superview constraint by single clicking it, and then press Delete to uninstall the constraint for the current size class. The constraint immediately disappears from the storyboard view and becomes grayed out in both the Document Outline and the view’s Size Inspector:
Note: You might have to toggle from This Size Class to All in the Size Inspector to see the uninstalled constraint.
Double click on the uninstalled constraint in the Size Inspector to select the constraint . There’s an additional line at the bottom as shown below:
This indicates the constraint is installed for the base layout, but not for the Any Width | Compact Height layout – that is, the one you’re currently editing.
Repeat the same procedure to uninstall the other three constraints associated with the image view. Your document outline and image view’s Size Inspector should resemble the following images:
Now you can add the constraints required for this size class. Use the Align and Pin menus to Vertically Center in the Container, and to set a left spacing to nearest neighbor of 10:
Ctrl-drag from the image view to the view controller’s view and then select Equal Widths in the popup menu.
Open the Size Inspector for the image view and double-click the Equal Width to: Superview constraint to reveal its properties. If the First Item isn’t cloud.Width, then use the drop down menu to Reverse First and Second Item. Then update the Multiplier to equal 0.45.
The constraints for the image view are now setup correctly for all size classes, but the text container still needs a bit of attention. You’ll need to alter the constraints for this size class to move the label to the right hand side.
The TextContainer view has internal constraints to position the labels, which work fine as-is. However, the three external constraints – pinning it to the left, right and bottom sides of the view – aren’t quite working properly. To pin the view to the bottom right hand side of the parent view you’ll need to uninstall the left-hand constraint.
Select the left-hand constraint, either from the document outline or on the storyboard:
Then press Cmd-Delete to uninstall the constraint. As before, any uninstalled constraints appear grayed out in the Document Outline.
You now need to add two constraints to your TextContainer to position it correctly. The view should be half the width of its superview, the view controller’s view, and pinned to the top.
In theory, you could simply Ctrl-drag from the TextContainer view to the view controller’s view as you’ve been doing all along. However, in practice it’s often difficult to grab just the view when there’s content in the way. It’s much easier to use the document outline to do your dirty work.
Ctrl-drag from TextContainer in the document outline up to the view controller’s view:
Shift-click on Top Space to Top Layout Guide and Equal Widths:
Open the Size Inspector for your TextContainer and update the two new constraints you’ve just created as follows:
- Top Space to: Top Layout Guide should have a Constant of 0
- Equal Width to: Superview should have a Multiplier of 0.5. Note that you might have to switch the first and second items in this constraint, as you have done before. Simply double click on the constraint and choose to Reverse First and Second Item.
Click the Resolve Auto Layout Issues icon in the bottom bar, and select All Views\Update frames. The storyboard updates and displays the new layout as shown below:
The layout has changed completely; you’re closer to the finished product now. There are still a few issues to correct with the font sizes – you’ll address these in the next section.
The current font sizes in your TextContainer look pretty good in the iPad view using a container with regular size classes, but the font size is too large for compact size classes. Fear not – it’s also possible to override font sizes within your size classes!
Note: Unlike layout overrides, changing the font configuration will always affect the base layout. Changes to the font configuration don’t respect the current size class overrides in interface builder. Instead, use the method outlined below.
Click the size class button in the bottom tool bar and select the squares to choose the base Any Width | Any Height size class. The bottom bar of your view turns gray to indicate that you’re back to the base layout.
Select the Cupertino text label and open the Attributes Inspector. Click the small + to the left of the Font:
This reveals a menu for selecting the size class combination in which you’ll override the font size. Navigate to Compact Width > Any Height like so:
This creates a second font selector box that will apply to the specified size class combination. Update the font size in the new selector box to 90:
Now select the temperature text label and repeat the same process; this time, set a font size override of 150 for Compact Width > Any Height.
The preview updates automatically to show the effect of the changes you made:
Well, it’s looking a little better, but the Cupertino label is clipped. Fiddling with the font sizes until it fits perfectly isn’t a particularly scalable option. Cupertino is quite a long place name, but Washington, D.C. is longer – and Kleinfeltersville, PA is longer still! What’s a designer to do?
Once again, Auto Layout comes to the rescue! You simply need to limit the width of the two text labels to match the width of the TextContainer. Ctrl-drag from the Cupertino label to the TextContainer, and select Equal Widths.
Repeat the process with the temperature label. The preview updates to show the effects of your changes as follows:
Hmm, having the text truncate is not exactly what you want. This is the default behavior for labels that contain more text than will fit in the available space. However, there is an option to automatically adjust the font size to fit the content.
Select the Cupertino label and open the Attributes Inspector. Change the AutoShrink drop down to Minimum font scale and ensure that it is set to 0.5. Also update the Text Alignment to Centered. Your Attributes Inspector should look as follows:
Repeat exactly the same procedure for the temperature label.
Take a look at the preview pane; the iPhone layouts look much better now:
Working with the preview editor is great, but this is probably a good time to build and run your project to see that everything is still working correctly. The iPhone screens look like everything is sizing correctly:
Congrats, you have learned the basic techniques of Adaptive Layout!
Where To Go From Here?
Here is the finished example project from this Adaptive Layout tutorial.
Take a moment to think about the app you’ve built (or the completed project you’ve downloaded). In particular, consider that it looks really good on all devices, in both orientations, using only one storyboard file!
If nothing else convinces you that Adaptive Layout is the way of the future, consider the fact that this layout will also look good on future devices that haven’t even been released yet.
The takeaway from this tutorial is that, as a developer, you will need to change your approach to app design. Rather than working towards pixel-based layouts, you should be considering the relationship between the UI elements on the screen.
If you want to learn more about Adaptive Layout, check out our book iOS 8 by Tutorials. In the book, we take this example a little bit further, and we have several more chapters on Adaptive Layout: Intermediate Adaptive Layout, Adaptive View Controller hierarchies, Transition Coordinators, and more.
In the meantime, if you have any questions or comments about this tutorial or Adaptive Layout in general, please join the forum discussion below!