The iPhone and iPad exude amazing engineering in form, fit, and function. Apple engineers found all kinds of ways to squeeze maximum functionality into a very small and elegant package. One example of this exists in the ability of these devices to be used in either portrait (tall and skinny) or landscape (short and wide) mode and how that orientation can be changed at runtime simply by rotating the device. You see an example of this autorotation behavior in the iOS Safari browser, as shown in Figure 5-1. In this chapter, I’ll cover rotation in detail, starting with an overview of the ins and outs of autorotation and then moving on to different ways of implementing that functionality in your apps.
Figure 5-1. Like many iOS applications, Mobile Safari changes its display based on how it is held, making the most of the available screen space
Prior to iOS 8, if you wanted to design an application that would run on both iPhones and iPads, you created one storyboard with a layout for the iPhone and another one with your iPad layout. In iOS 8 , that all changed when Apple added APIs to UIKit and tools in Xcode, making it possible to build an application that runs on (or, using its terminology, adapts to) any device with a single storyboard. You still must design carefully for the different form factor of each type of device, but now you do it all in one place. Even better, using the Preview feature that was introduced in Chapter 3, you see immediately how your application would look on any device without even having to start up the simulator. You’ll take a look at how to build adaptive application layouts in the second part of this chapter.
Understanding the Mechanics of Rotation
The ability to run in both portrait and landscape orientations might not work for every application. Several of Apple’s iPhone applications, such as the Weather app, may support only a single orientation. However, iPad applications function differently, with Apple recommending that most apps, with the exception of immersive apps like games, should support every orientation, and most of Apple’s own iPad apps work fine in both orientations. Many of them use the orientations to show different views of your data. For example, the Mail and Notes apps use landscape orientation to display a list of items (folders, messages, or notes) on the left and the selected item on the right. In portrait orientation, however, these apps let you focus on the details of just the selected item.
For iPhone apps, the base rule is that if autorotation enhances the user experience, you should add it to your application. For iPad apps, the rule is you should add autorotation unless you have a compelling reason not to. Fortunately, Apple did a great job of hiding the complexities of handling orientation changes in iOS and in UIKit, so implementing this behavior in your own iOS applications becomes quite easy.
The view controller authorizes the image to rotate. If the user rotates the device, the active view controller gets asked if it’s okay to change to the new orientation (which you’ll do in this chapter). If the view controller responds in the affirmative, the application’s window and views rotate, and the window and view resize to fit the new orientation.
On the iPhone and iPod touch, a view that starts in portrait mode exists taller than it is wide—you can see the actual available space for any given device by referring to the Software Size column of Table 1-1 in Chapter 1. Note, however, that the vertical screen real estate available for your app decreases by 20 points vertically if your app is showing the status bar, which is the 20-point strip at the top of the screen (see Figure 5-1) that shows information such as signal strength, time, and battery charge.
When the device rotates to landscape mode , the vertical and horizontal dimensions switch around, so, for example, an application running on an iPhone 6/6s would see a screen that’s 375 points wide and 667 points high in portrait but that’s 667 points wide and 375 points high in landscape. Again, though, on iPads the vertical space actually available to your app gets reduced by 20 points if you’re showing the status bar, which most apps do. On iPhones, as of iOS 8, the status bar hides when in landscape orientation.
Understanding Points, Pixels, and the Retina Display
You might be wondering why I’m talking about “points” instead of pixels. Earlier versions of this book did, in fact, refer to screen sizes in pixels rather than points . The reason for this change is Apple’s introduction of the Retina display, which is Apple’s marketing term for the high-resolution screen on all versions of the iPhone starting with iPhone 4 and later-generation iPod touches, as well as newer variants of the iPad. As you can see by looking back at Table 1-1 again, it doubles the hardware screen resolution for most models and almost triples it for the iPhone 6s/7 Plus.
Fortunately, you don’t need to do a thing in most situations to account for this. When you work with on-screen elements, you specify dimensions and distances in points, not in pixels. For older iPhones and the iPad, iPad 2, and iPad Mini 1, points and pixels are equivalent—one point is one pixel. On more recent-model Apple devices, however, a point equates to a 4-pixel square (2 pixels wide × 2 pixels high), and the iPhone 5s screen (for example) still appears to be 320 points wide, even though it’s actually 640 pixels across. On iPhone 6s/7 Plus, the scaling factor is 3, so each point maps to a 9-pixel square. Think of it as a “virtual resolution,” with iOS automatically mapping points to the physical pixels of your screen.
In typical applications, most of the work of actually moving the pixels around the screen is managed by iOS. Your app’s main function in all this is making sure everything fits nicely and looks proper in the resized window.
Handling Rotation
To handle device rotation, you need to specify the correct constraints for all the objects making up your interface. Constraints tell iOS how the controls should behave when their enclosing view is resized. How does that relate to device rotation? When the device rotates, the dimensions of the screen are (more or less) interchanged—so the area in which your views are laid out changes size.
The simplest way of using constraints is to configure them in Interface Builder (IB) . Interface Builder lets you define constraints that describe how your GUI components will be repositioned and resized as their parent view changes or as other views move around. You did a little bit of this in Chapter 4, and you will delve further into the subject of constraints in this chapter. You can think of constraints as equations that make statements about view geometry and the iOS view system itself as a “solver” that will rearrange things as necessary to make those statements true. You can also add constraints in code, but I’m not going to cover that in this book.
Constraints were added to iOS 6 but have been present on the Mac for a bit longer than that. On both iOS and macOS, constraints can be used in place of the old “springs and struts” system that was found in earlier releases. Constraints can do everything the old technology could do, and more.
Creating Your Orientations Project
You’ll create a simple app to see how to pick the orientations that you want your app to work with. Start a new Single View App project in Xcode, and call it Orientations. Choose Universal from the Devices pop-up, and save it along with your other projects.
Before you lay out your GUI in the storyboard, you need to tell iOS that your view supports interface rotation. There are actually two ways of doing this. You can create an app-wide setting that will be the default for all view controllers, and you can further tweak things for each individual view controller. You’ll do both of these things, starting with the app-wide setting.
Understanding Supported Orientations at the App Level
First, you need to specify which orientations your application supports. When your new Xcode project window appeared, it should have opened to your project settings. If not, click the top line in the Project Navigator (the one named after your project) and then make sure you’re on the General tab. Among the options available in the summary, you should see a section called Deployment Info, and within that, a section called Device Orientation (see Figure 5-2) with a list of check boxes .
Figure 5-2. The General tab for your project shows, among other things, the supported device orientations
This is how you identify which orientations your app supports. It doesn’t necessarily mean that every view will use all of the selected orientations, but if you are going to support an orientation in any of the views, that orientation must be selected here. Notice that the Upside Down orientation is off by default. That’s because Apple does not encourage the user to hold the phone upside down because if the phone rings while it is in that orientation, the user would have to twist it through a full half-turn to answer it.
Open the Devices drop-down that’s just above the check boxes (see Figure 5-3) and you’ll see that you can actually configure separate sets of allowed orientations for the iPhone and the iPad. If you choose iPad, you’ll see that all four check boxes are selected because the iPad is meant to be used in any orientation.
Figure 5-3. You can configure different orientations for the iPhone and iPad
Note
The four check boxes shown in Figures 5-2 and 5-3 are actually just a shortcut to adding and deleting entries in your application’s Info.plist file . If you single-click Info.plist in the Project Navigator, you should see two entries called “Supported interface orientations ” and “Supported interface orientations (iPad),” with subentries for the orientations that are currently selected. Selecting and deselecting those check boxes in the project summary simply adds and removes items from these arrays. Using the check boxes is easier and less prone to error, so using the check boxes is definitely recommended. However, you should know what they do.
Again, you’ll work with an iPhone 6s as your device. Now, select Main.storyboard. Find a label in the Object Library and drag it into your view, dropping it so that it’s horizontally centered and somewhere near the top, as shown in Figure 5-4. Select the label’s text and change it to This way up. Changing the text may shift the label’s position, so drag it to make it horizontally centered again.
Figure 5-4. Setting your Portrait orientation label
You need to add Auto Layout constraints to pin the label in place before running the application, so Control-drag from the label upward until the background of the containing view turns blue and then release the mouse. Hold down the Shift key and select Vertical Spacing to Top Layout Guide and Center Horizontally in Container in the pop-up and then press Return. Now, press ⌘R to build and run this simple app on the iPhone simulator. When it comes up in the simulator, try rotating the device a few times by pressing ⌘-Left Arrow or ⌘-Right Arrow. You’ll see that the entire view (including the label you added) rotates to every orientation except upside down, just as you configured it to do. Run it on the iPad simulator to confirm that it rotates to all four possible orientations.
You’ve identified the orientations your app will support, but that’s not all you need to do. You can also specify a set of accepted orientations for each view controller, giving you more fine-grained control over which orientations will work in different parts of your apps.
Understanding Per-Controller Rotation Support
Let’s configure your view controller to allow a different, smaller set of accepted orientations. The global configuration for the app specifies a sort of absolute upper limit for allowed orientations. If the global configuration doesn’t include upside-down orientation, for example, there’s no way that any individual view controller can force the system to rotate the display to upside down. All you can do in the view controller is place further limits on what is acceptable.
In the Project Navigator, single-click ViewController.swift. Here you’ll implement a method defined in the UIViewController superclass that lets you specify which subset of the global set of orientations you’ll accept for this view controller:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask(rawValue:
(UIInterfaceOrientationMask.portrait.rawValue
| UIInterfaceOrientationMask.landscapeLeft.rawValue))
}
This method lets you return a UIInterfaceOrientationMask that specifies the acceptable orientations. Calling this method is iOS’s way of asking a view controller if it’s allowed to rotate to a specific orientation. In this case, we’re returning a value that indicates that we’ll accept two orientations: the default portrait orientation and the orientation you get when you turn your phone 90° clockwise so that the phone’s left edge is at the top. You use the Boolean OR operator (the vertical bar symbol) to combine the raw values of these two orientation masks and use the result to create a new UIInterfaceOrientationMask that represents the combined value.
UIKit defines the following orientation masks, which you can combine in any way you like using the OR operator (shown in the preceding example):
UIInterfaceOrientationMask.portrait.rawValue
UIInterfaceOrientationMask.landscapeLeft.rawValue
UIInterfaceOrientationMask.landscapeRight.rawValue
UIInterfaceOrientationMask.portraitUpsideDown.rawValue
In addition, there are some predefined combinations of these for common use cases. These are functionally equivalent to ORing them together on your own but can save you some typing and make your code more readable.
UIInterfaceOrientationMask.landscape.rawValue
UIInterfaceOrientationMask.all.rawValue
UIInterfaceOrientationMask.allButUpsideDown.rawValue
When the iOS device changes to a new orientation, the supportedInterfaceOrientations() method is called on the active view controller . Depending on whether the returned value includes the new orientation, the application determines whether it should rotate the view. Because every view controller subclass can implement this differently, it is possible for one application to support rotation with some of its views but not with others, or for one view controller to support certain orientations under certain conditions. Run the example application again and verify that you can now rotate the simulator only to the two orientations that are returned by the supportedInterfaceOrientations() method. The .rawValue at the end of each orientation returns the integer value for the orientation to be used in comparison.
Note
You can actually rotate the device, but the view itself does not rotate, so the label is back to the top except for the two selected orientations.
Feel free to play around with this method by returning different orientation mask combinations. You can force the system to constrict your view’s display to whichever orientations make sense for your app, but don’t forget the global configuration I talked about earlier. Remember that if you haven’t enabled upside down there (for example), none of your views will ever appear upside down, no matter what their view controller’s supportedInterfaceOrientations() method declares.
Note
iOS actually supports two different types of orientations. The one I’m discussing here is the interface orientation. There’s also a separate but related concept of device orientation, which specifies how the device is currently being held. Interface orientation is which way the views on the screen are rotated. If you turn a standard iPhone upside down, the device orientation will be upside down, but the interface orientation will almost always be one of the other three since iPhone apps don’t support portrait upside down by default.
Creating Your Layout Project
In Xcode, make another new project based on the Single View App template and name it Layout. Select Main.storyboard to edit the storyboard in Interface Builder. A great thing about constraints is that they accomplish quite a lot using very little code. To see how this works, drag four labels from the library to your view and place them as shown in Figure 5-5. Use the dashed blue guidelines to help you line up each one near its respective corner. In this example, you’re going to use instances of the UILabel class to see how to use constraints to build your GUI layout, but the same rules apply to many GUI objects.
Figure 5-5. Adding four labels to your storyboard
Double-click each label and assign a title to each one so that you can tell them apart later. I’ve used UL for the upper-left label, UR for the upper-right label, LL for the lower-left label, and LR for the lower-right label. After setting the text for each label, drag all of them into position so that they are lined up evenly with respect to the container view’s corners.
Let’s see what happens now, given that you haven’t yet set any Auto Layout constraints. Build and run the app on the iPad Air simulator. Once the simulator starts up, you’ll find that you can only see the labels on the left—the other two are off-screen to the right. Furthermore, the label at the bottom left is not where it should be—right in the bottom-left corner of the screen. Select Hardware ➤ Rotate Left, which will simulate turning the iPad to landscape mode. You’ll find that you can now see the top-left and top-right labels, as shown in Figure 5-6.
Figure 5-6. Changing orientation without adding any constraints
As you can see, things aren’t looking so good. The top-left label is in the right spot after rotating, but all of the others are in the wrong places, and some of them aren’t visible at all! What’s happened is that every object has maintained its distance relative to the upper-left corner of the view in the storyboard. What you really want is to have each label sticking tightly to its nearest corner after rotating. The labels on the right should shift horizontally to match the view’s new width, and the labels on the bottom should move vertically to match the new height. Fortunately, you can easily set up constraints in Interface Builder to make these changes happen for you.
As you’ve seen in earlier chapters, Interface Builder is smart enough to examine this set of objects and create a set of default constraints that will do exactly what you want. It uses some rules of thumb to figure out that if you have objects near edges, you probably want to keep them there. To make it apply these rules, first select all four labels. You can do this by clicking one label and then holding down the Shift key while clicking each of the other three. With all of them selected, choose Editor ➤ Resolve Auto Layout Issues ➤ Add Missing Constraints from the menu (you’ll find there are two menu items with this name—in this case, because you have selected all of the labels, you can use either of them). Next, just press the Run button to launch the app in the simulator and then verify that it works.
Note
Another way to easily select all the labels is to Shift-click the label names in the Document Outline, as shown in Figure 5-7.
Figure 5-7. Using the Document Outline view (to the left of the storyboard canvas) can sometimes make it easier to select and work with multiple UI objects
Knowing that this works is one thing, but to use constraints like this most effectively, it’s pretty important to understand how it works, too. So, let’s dig into this a bit. Back in Xcode, click the upper-left label to select it. You’ll notice that you can see some solid blue lines attached to the label. These blue lines are different from the dashed blue guidelines that you see when dragging objects around the screen, as shown in Figure 5-8.
Figure 5-8. The solid blue lines show constraints that are configured for the chosen object
Each of those solid blue lines represents a constraint. If you now press ⌥⌘5 to open the Size Inspector , you’ll see that it contains a list of constraints. Figure 5-9 shows the constraints that Xcode applied to the UL label in my storyboard, but the constraints that Xcode creates depends on exactly where you placed the labels, so you may see something different.
Figure 5-9. Four constraints generated by Xcode to pin a label in its parent view
In this case, two of the constraints deal with this label’s position relative to its superview, which is the container view : it specifies the leading space, which generally means the space to the right, and the top space, which means the space above the label. These constraints cause the label to maintain the same distance to the top and right edges of its superview when the superview’s size changes, as it does when the device is rotated. The other two constraints keep this label lined up with two of the other labels. Examine each of the other labels to see what constraints they have and make sure that you understand how those constraints work to keep the four labels in the corners of their superview.
You should know that in languages where text is written and read from right to left, leading space is on the right, so adding a trailing constraint will cause a GUI to be laid out in the opposite direction if the user has picked a language such as Arabic for their device. This is, in fact, what the user would expect. It’s automatic, so you don’t need to do anything special to make it happen.
Overriding Default Constraints
Grab another label from the library and drag it to the layout area. This time, instead of moving toward a corner, drag it toward the left edge of your view, lining up the label’s left edge with the left edges of the other labels on the left side and centering it vertically in the view. Dashed lines will appear to help you out. Figure 5-10 shows you what this looks like.
Figure 5-10. Placing the Left label
Let’s add a new constraint to force this label to stay vertically centered. Select the label, click the Align icon below the storyboard, select Vertically in Container in the pop-up that appears, and then click Add 1 Constraint. Now make sure that the Size Inspector is on display (by pressing ⌥⌘5 if necessary). You’ll see that this label now has a constraint aligning its center Y value to that of its superview. The label also needs a horizontal constraint. You can add this by making sure the label is selected and then choosing Editor ➤ Resolve Auto Layout Issues ➤ Add Missing Constraints from the All Views section of the menu. Press ⌘R to run the app again. Do some rotating and you’ll see that all the labels now move perfectly into their expected places for the various device types.
Now, let’s complete your ring of labels by dragging out a new one to the right side of the view, lining up its right edge with the other labels on the right, and aligning it vertically with the Left label. Change this label’s title to Right and then drag it a bit to make sure that the right edge is vertically aligned with the right edges of the other two labels, using the dashed blue line as your guide. You want to use the automatic constraints that Xcode can provide you with, so select Editor ➤ Resolve Auto Layout Issues ➤ Add Missing Constraints to generate them.
Build and run again. Do some rotating again. You’ll see that all the labels stay on the screen and are correctly positioned relative to each other (see Figure 5-11). If you rotate back, they should return to their original positions. This technique works great for many applications you’re likely to encounter.
Figure 5-11. The labels in their new positions after rotating
Using Full-Width Labels
You’re going to create some constraints that make sure that your labels stay the same width as each other, with tight spacing to keep them stretched across the top of the view even when the device rotates. Figure 5-12 should give you an idea of what you’re trying to do.
Figure 5-12. The top labels , spread across the entire width of the display, in both portrait and landscape orientations
You need to be able to visually verify that you have the result you want—namely, each label is precisely centered within its half of the screen. To make it easier to see whether you have it right, let’s temporarily set a background color for the labels. In the storyboard, select both the UL and UR labels, open the Attributes Inspector, and scroll down to the View section. Use the Background control to select a nice, bright color. You’ll see that the (currently very small) frame of each label fills with the color you chose.
Drag the resizing control of the UL label from its right edge, pulling it almost to the horizontal midpoint of the view. You don’t have to be exact here, for reasons that will become clear soon. After doing this, resize the UR label by dragging its left-edge resizing control to the left until you see the dashed blue guideline appear (if you don’t see the guide disappear, just drag it reasonably close), which tells you that it’s the recommended width from the label to its left. Now you’ll add a constraint to make these labels retain their relative positions. Control-drag from the UL label until the mouse is over the UR label and then release the mouse. In the pop-up, select Horizontal Spacing and press Return. That constraint tells the layout system to hold these labels beside one another with the same horizontal space they have right now. Build and run to see what happens. You should see something like Figure 5-13; the longer label may appear on the left or right depending upon your configuration.
Figure 5-13. The labels are stretched across the display but not evenly
That’s heading in the right direction but not yet what I had in mind. So, what’s missing? You’ve defined constraints that control each label’s position relative to its superview and the allowed distance between the two labels, but you haven’t said anything about the sizes of the labels. This leaves the layout system free to size them in whatever way it wants (which, as you’ve just seen, can be quite wrong). To remedy this, you need to add one more constraint.
Make sure the UL label is selected and then hold down the Shift key (⇧) and click the UR label. With both labels selected, you can make a constraint that affects both of them. Click the Pin icon below the storyboard and select the Equal Widths check box in the pop-up that appears (which you saw in Chapter 3); then click Add 1 Constraint. You’ll now see a new constraint appear, as shown in Figure 5-14. You may notice two orange lines have appeared below the labels; this means that the current positions and sizes of the labels in the storyboard do not match what you will see at runtime. To fix this, select the View icon in the Document Outline and then select Editor ➤ Resolve Auto Layout Issues ➤ Update Frames in Xcode’s menu. The constraints should change to blue, and the labels will resize themselves so that their widths are equal.
Figure 5-14. The top labels are now made equal in width by a constraint
If you run again at this point, you should see the labels spread across the entire screen, in both portrait and landscape orientations (see Figure 5-12).
In this project, all of your labels are visible and are correctly laid out in multiple orientations; however, there is a lot of unused space on the screen. Perhaps it would be better if you also set up the other two rows of labels to fill the width of the view or allowed the height of your labels to change so that there will be less empty space on the interface? Feel free to experiment with the constraints of these six labels and perhaps even add some others. Apart from what I’ve covered so far, you’ll find more actions that create constraints in the pop-ups that appear when you click the Pin and Align icons below the storyboard. And if you end up making a constraint that doesn’t do what you want, you can delete it by selecting it and pressing the Delete key, or you can try configuring it in the Attributes Inspector. Play around until you feel comfortable with the basics of how constraints work. You’ll use constraints constantly throughout the book, but if you want the full details, just search for Auto Layout in the Xcode documentation window.
Creating Adaptive Layouts
The layout for the simple example that you just created works well in portrait and landscape orientations. It also works on both iPhone and iPad, despite their differing screen dimensions. As I already noted, handling device rotation and creating a user interface that works on devices with different screen sizes are really the same problem—after all, from the point of view of your application, when the device rotates, the screen effectively changes size. In the simplest cases, you handle them both at the same time by assigning Auto Layout constraints to make sure that all of your views are positioned and sized where you want them to be. However, that’s not always possible. Some layouts work well when the device is in portrait mode but not so well when it’s rotated to landscape; similarly, some designs suit the iPhone but not the iPad. When this happens, you really have no choice but to create separate designs for each case. Prior to iOS 8, this meant either implementing your whole layout in code, having multiple storyboards, or doing a combination of the two. Fortunately, Apple has made it possible to design adaptive applications that work in both orientations and on different devices while still using only a single storyboard. Let’s take a look at how this works.
Creating the Restructure Application
To get started, you’ll design a user interface that works well for an iPhone in portrait mode but not so well when the phone is rotated or when the application runs on an iPad. Then you’ll see how to use Interface Builder to adapt the design so that it works well everywhere.
Start by making a new Single View app like you’ve done before, naming this one Restructure. You’re going to construct a GUI that consists of one large content area and a small set of buttons that perform various (fictional) actions. You’ll place the buttons at the bottom of the screen and let the content area take up the rest of the space, as shown in Figure 5-15.
Figure 5-15. The initial GUI of the Restructure app, in portrait orientation on the iPhone
Note
You may have noticed that some of the various illustrations of Apple devices I use in this book have different configurations. While some, such as Figure 5-15, have an appearance closer to a “real” device, others such as Figure 5-11, appear more basic. The differences should not be a concern as you’re likely to come across any of them in technical documentation, including that from Apple.
Select Main.storyboard to start editing the GUI. Since you don’t really have an interesting content view you want to display, you’ll just use a large colored rectangle. Drag a single UIView from the Object Library into your container view. While it’s still selected, resize it so that it fills the top part of the available space, leaving a small margin above it and on both sides, as shown in Figure 5-15. Next, switch to the Attributes Inspector and use the Background pop-up to pick some other background color. You can choose anything you like, as long as it’s not white, so that the view stands out from the background. In the storyboard in the example source code archive, this view is green, so from now on I’ll call it the green view.
Drag a button from the Object Library and place it in the lower left of the empty space below the green view. Double-click to select the text in its label, and change it to Action One. Now Option-drag three copies of this button and place them in two columns, like those in Figure 5-15. You don’t have to line them up perfectly because you’re going to use constraints to finalize their positions, but you should try to place the two button groups approximately equal distances from their respective sides of the containing view. Change their titles to Action Two, Action Three, and Action Four. Also, let’s add a different background color to each button so they’re easy to see; I’ve used red, blue, orange, and yellow in order, but you can choose any colors you prefer. If you use a dark background color like blue, you want to make the text lighter as well. Finally, drag the lower edge of the green view downward until it just touches the top row of buttons. Use the blue guidelines to line everything up, as shown in Figure 5-15.
Now let’s set up the Auto Layout constraints . Start by selecting the green view. You’re going to start by pinning this to the top and to the left and right sides of the main view. That’s still not enough to fully constrain it because its height isn’t specified yet; you’re going to fix that by anchoring it to the top of the buttons, once you’ve fixed the buttons themselves. Click the Pin button at the bottom right of the storyboard editor. At the top of the pop-up, you’ll see the now familiar group of four input fields surrounding a small square. Leave the Constrain to Margins check box selected. Click the red dashed lines above, to the left, and to the right of the small square to attach the view to the top, left, and right sides of its superview (see Figure 5-16). Click Add 3 Constraints.
Figure 5-16. Adding constraints to fix the green view to the top, left, and right sides
For now, you’ll set a constant height for your buttons, starting with Action One, as shown in Figure 5-17. I’ve used the value of 43 points simply because that’s where it was when I created the button. Anything around this number should be okay for what you’re trying to do in this example, which is deal with different devices and orientations. Then repeat the operation for each of the other three buttons.
Figure 5-17. Setting a height value for one of your buttons
If you performed the operations correctly so far, you should be able to see the results of all the constraints in the Document Outline , as shown in Figure 5-18, where you see each of your four button heights, as well as the three sides for the green view.
Figure 5-18. You can always see the progress of setting your constraints in the Document Outline
Next pin the bottom-left (Action Two) and bottom-right (Action Four) buttons to the lower corners by Control-dragging from each button to the lower left and lower right, respectively. For Action Two, Shift-select the two options, as shown in Figure 5-19.
Figure 5-19. Control-drag down and to the left to pin the Action Two button to the lower left of the container view
Perform the similar operation, Control-dragging out to the right and down to set the leading and vertical spacing for the Action Four button, as shown in Figure 5-20.
Figure 5-20. Control-drag down and to the right to pin the Action Four button to the lower right of the container view
Next, Shift-select all four buttons, click the Pin icon, and set all the widths to be equal (see Figure 5-21). Note that you haven’t set a width yet, so they could vary from small to extremely wide; but in a moment, you’ll take care of that through additional constraints.
Figure 5-21. Set all the buttons to be equal widths, although you haven’t yet set any value for width in your storyboard
For the top row of buttons, Action One and Action Three, Control-drag to the left and right, respectively, to set Leading Space to Container Margin and Trailing Space to Container Margin (see Figure 5-22). This ties the left edge of Action One and the right edge of Action Three to the edge of the view, setting one of your width anchor points.
Figure 5-22. Setting the left edge of Action One and the right edge of Action Three to the edges of the containing view
These final two constraints will be all you need to make sure that the buttons are half the width of the green view and match each other. Control-drag from Action One to Action Three and set the horizontal spacing. Do the same thing between Action Two and Action Four, as shown in Figure 5-23. What you’ve done is to set the edge anchor to a fixed location, which is the left and right edges of the container in Figure 5-22. And, as you saw in Figure 5-21, you set the buttons to be equal width. By setting the horizontal spacing so that the buttons on the same row are up against each other, they work out to meet in the center of the view. And since the green view is also pinned to the left and right edges, the buttons meet in the middle of the green view.
Figure 5-23. Attach each row of buttons together at the center of the view; this, by default, sets the width of each button to be half the width of the view
Just a couple more things and you’ll be ready to test your app in the various devices. Control-drag from Action Three to Action Four to set the vertical spacing , like you just did with the horizontal spacing of rows, as shown in Figure 5-24. Do the same between Action One and Action Two.
Figure 5-24. Set the vertical spacing between buttons in the same column like you did with the button rows
Finally, Control-drag between the green view and the Action One button to set the spacing so that the green view and the top row of buttons are adjacent, as shown in Figure 5-25.
Figure 5-25. Setting the spacing between your green view and the top row of buttons
Now you should be able to select any full-screen iPhone or iPad in either portrait or landscape orientation and the buttons should maintain their position , as shown in Figure 5-26.
Figure 5-26. Selecting different devices in portrait orientation; the layouts should maintain their positioning
One other thing to note when looking at the Issue Navigator in Xcode is that there aren’t any issues. By systematically setting just the constraints you needed, you were able to create a layout with minimal effort. However, don’t expect it to be this easy all the time.
One last thing to do: let’s build and run your project in the simulator before moving forward just to make sure it works as you expect. Figure 5-27 shows the two iPhone 6s orientations , while Figure 5-28 shows the two orientations for a full-screen iPad Air.
Figure 5-27. Your two orientations for an iPhone 6s
Figure 5-28. Your two orientations for an iPad Air
While these look like you would expect them to, they’re not what you’re after. For an iPhone in landscape, still a wC hC configuration, you want a single column of buttons pressed up against the right side. For an iPad, in any orientation, wR hR, you want a single row of buttons against the bottom of the view.
Note
The notation w- h- refers to the width and height of the configuration being considered. To simplify things, in Auto Layout this will be either C for compact or R for regular so that you have these options: wC hC, wC hR, wR hC, and wR hR. By looking in the Device Configuration Bar, you can see how these apply to actual Apple devices.
Setting the iPhone Landscape (wC hC) Configuration
You’ll get to setting up your wC hC landscape configuration quickly, but first save your work and then close the Xcode project. You can just click the red ball at the upper left of the Xcode window to close this project. You don’t have to exit Xcode completely.
Go to Finder on your Mac to locate the Restructure folder and create a compressed version, as shown in Figure 5-29. This creates your “master” copy of the project that you can come back to at any time if, during the following changes, things get out of whack.
Figure 5-29. Create a master copy of your project as it exists now
Because you may want to do this again through the course of your following work, rename the .zip file to RestructureBaseline, as shown in Figure 5-30, so you know that this is the original project you made that works with all devices and orientations in the same manner.
Figure 5-30. Saving your baseline project under a unique name
First, let’s create your iPhone landscape orientation . Select the iPhone 6s and the landscape orientation. To the right of the Device Configuration Bar, click Vary for Traits and note that the bar turns blue, as shown in Figure 5-31. In the pop-up select both height and width. You should see, again in Figure 5-31, that now only iPhone devices are shown and only in landscape orientation. In this variation of traits, you’ll develop your UI just for this configuration.
Figure 5-31. The starting point for creating the UI for your iPhone landscape configuration
Next, and yes this will seem scary, click all five UI elements (your green view and the four buttons) and press Delete. Don’t worry too much because you saved the project and created a compressed baseline version that you can always get back to later. If you didn’t, it might be a good idea to do that now. When completed, your canvas will look, as you’d expect, like Figure 5-32. But, if you look at the Document Outline, you’ll still see the elements and even the constraints. That’s because they exist for the baseline configuration and you’re creating a new configuration or a new trait collection for just your wC hC landscape.
Figure 5-32. Starting over for just your iPhone landscape configuration. Note that you still have all the UI elements and constraints showing in your Document Outline but for your baseline configuration.
As you did for your baseline, drag out a UIView and four buttons, setting them up with colors and titles as you did earlier. Place them in the approximate positions shown in Figure 5-33, but don’t set any constraints just yet.
Figure 5-33. Drag new UI elements onto the storyboard and position them approximately as shown
In this section, let’s make your measurements a little more accurate. Select the green view. Using the Size Inspector, set the dimensions to 500 × 340 points, as shown in Figure 5-34. The width (500pts) is arbitrary; the height is 340, which when divided by 4 comes out to 85, which is what you will set as the height of the buttons. Note that this is not necessarily a constraint but, rather, the appearance on the storyboard. In fact, you don’t want any fixed size for the green view since it will vary depending on whether you go up in size to a Plus or down to an SE.
Figure 5-34. Set the dimensions of the green view
Select the green view and pin the top, left, and right sides to the edge, as shown in Figure 5-35.
Figure 5-35. Pin the green view to the top, left, and right for the landscape orientation
Next, you’re going to fix the width of the Action One button , but you’re going to do it a little differently than previously. Inside the Action One button, Control-drag from one point to another both the starting and ending points within the red boundary and release. You’re presented with a similar popover (see Figure 5-36), on which you select Width. The width of the Action One button should now be 120 points, as shown in Figure 5-37.
Figure 5-36. Set the width of the Action One button
Figure 5-37. Verify that the Action One button is set to 120 points
You want all four buttons to be the same width and the same height—the width being 120 points but the height being adjust dynamically based on the available vertical area of the iPhone when in landscape orientation. As before, you’ll handle the equal height by setting the spacing between the buttons in the column. For now, Shift-click all four buttons to select them all, click the Pin icon, and set the Equal Widths and Equal Heights constraints, as shown in Figure 5-38.
Figure 5-38. Set all four buttons to equal width and height
Note
You may have noticed that although you have four buttons with two constraints each, the actual number of constraints you add is 6. This is because you’re actually setting the equal quality of three buttons to the fourth button .
Pin the Action One button to the top and right of its containing view (see Figure 5-39) and the Action Four button to the right and bottom (see Figure 5-40).
Figure 5-39. Pinning the Action One button to the top and right
Figure 5-40. Pinning the Action Four button to the bottom and right
For the Action Two button , Control-drag to the right and choose Trailing Space to Container Margin to pin it to the right side of the container, as shown in Figure 5-41. Repeat this operation with the Action Three button.
Figure 5-41. Pin the middle buttons to the right side using Control-drag and set the Trailing Space to Container Margin pop-up, as shown here for Action Two. Do the same operation for the Action Three button.
Between the Action One and Action Two buttons , Control-drag to set the vertical spacing, as shown in Figure 5-42. Repeat for the spacing between Action Two and Action Three, as well as Action Three and Action Four.
Figure 5-42. Set the vertical spacing between each of the pairs of buttons in the column. This forces the height of each button to be one-fourth the height of the container.
Finally, set the horizontal spacing between the green view and the Action One button (you could use any of the four buttons), as shown in Figure 5-43.
Figure 5-43. Set the horizontal spacing between the green view and the row of buttons
Click the Done Varying button in the Device Configuration Bar to finish adding, placing, and setting constraints for this iPhone landscape configuration, as shown in Figure 5-44.
Figure 5-44. Finish by clicking the Done Varying button in the Device Configuration Bar
The buttons should now be properly placed for any of the three iPhone wC hC configurations, as shown in Figure 5-45. Note that if you were to select a 6/6s Plus, because that is a wR hC device, you would see the earlier baseline layout.
Figure 5-45. Changing the device type in the Device Configuration Bar shows that you have correctly set up your constraints for the landscape orientation of wC hC iPhone devices
Running the apps in the simulator should yield the results shown in Figure 5-46. Although you could have made the alignment a little tighter to the edges, which would be appropriate for a production app, in this example you wanted to stress the manipulations of UI elements for the various device and orientation configurations. As you become more and more familiar with Auto Layout, you’ll naturally get much better with your designs.
Figure 5-46. If you’ve done everything correctly, you should see the proper layout for any iPhone (except 6s/7 Plus) landscape configurations
The last thing you want to do before moving on to iPad is to save this project version by compressing it for later and assigning a recognizable name. As shown in Figure 5-47, I used the name Restructure_wChC.zip for your file, which represents the compact width and height. Feel free to use any naming convention you like, as long as you’re able to keep track of the various iterations.
Figure 5-47. Save this version of the project in case you need to get back to this baseline
Setting the iPad (iPhone Plus Landscape ) (wR hR) Configurations
In the previous two sections , I walked you through each step along the way to creating your layouts for the baseline and iPhone landscape configurations. You need to be able to quickly use Auto Layout when designing your UI, so if you need any review, I suggest running through the preceding sections another couple of times, trying it without looking at the text or figures.
For this configuration, to save space, I’ve shown the key steps in Table 5-1 along with pointing to the reference figures in case you might need a little help with understanding what the step is about. You’ll work this way from now on, at least most of the time unless I need to address something new.
Table 5-1. Setting Up All Orientations on an iPad and the Landscape Orientation on iPhone 6/6s Plus
Step | Action | Figure |
---|---|---|
1 | Click Vary For Traits and introduce variations based on width. | |
2 | Delete the five UI elements in the storyboard. Then add back five new elements from the Object Library as you did in the previous section. | |
3 | Select the green view you just added and, in the Size Inspector, set the width to 728 and the height to 926. These are not constraints; they’ll just help you with visually laying out the elements on the storyboard. (Note: These values work for the iPhone 6s. If using a different device size, you will need to vary your height and width accordingly.) | |
4 | Select the Action Four button and set its width to 182 using the Size Inspector; again, this is for the iPhone 6s device. This is only for visual placement and not a constraint. | |
5 | Making sure you still have a blue Device Configuration Bar indicating that you’re still in the Vary for Traits mode, align the UI elements as shown: the green view along the top and a single row of buttons along the bottom. | |
6 | Similar to what you did before, pin the green view to the top, left, and right sides of the containing view. | |
7 | Pin the Action One button to the lower-left corner of the containing view. | |
8 | Pin the Action Four button to the lower-right corner of the containing view. | |
9 | Pin the Action Two button to the bottom edge of the containing view. | |
10 | Pin the Action Three button to the bottom edge of the containing view. | |
11 | Add a constraint to set the height of the Action One button to a fixed value. I used 63 points because it fit the layout on my storyboard. There is no “right” answer; adjust it to your needs for your layout. | |
12 | Shift-select all four action buttons along the bottom row and set them to equal height and width. | |
13 | Click-drag from the green view to the Action One button and set the vertical spacing. This forces the green view to sit against the row of buttons. | |
14 | Click-drag from Action One to Action Two setting the horizontal spacing. Repeat for Action Two to Action Three as well as Action Three to Action Four. This causes the buttons to sit next to each other on the sides and to be one-quarter the width of the enclosing container. | |
15 | Click the Done Varying button to end the modifications for this set of traits. |
Follow the steps in Table 5-1 to set up the configuration for the iPad in all orientations and the iPhone 6/6s Plus in landscape orientation.
Figure 5-48. Select an iPad, click Vary For Traits, and choose Width
Figure 5-49. Delete the five UI elements
Figure 5-50. Add in the five new UI elements from the UI Object Library and set the width and height of the green view using the Size Inspector. This does not set constraints, only the visual aspects so you can adjust your storyboard.
Figure 5-51. Similarly, set the width and height of the Action Four button so you have a visual reference with which to work and manipulate your storyboard
Figure 5-52. Align everything up as shown, making sure your Device Configuration Bar is still blue indicating you’re working with a particular set of traits
Figure 5-53. Similar to what you did in the previous sections, pin the green view to the top, left, and right sides of the containing view
Figure 5-54. Pin the Action One button to the lower-left corner of the containing view
Figure 5-55. Pin the Action Four button to the lower-right corner of the containing view
Figure 5-56. Pin the Action Two button to the bottom edge of the containing view
Figure 5-57. Pin the Action Three button to the bottom edge of the containing view
Figure 5-58. Add a constraint to set the height of the Action One button to a fixed value. I used 63 points because it fit the layout on my storyboard .
Figure 5-59. Shift-select all four action buttons along the bottom row and set them to equal height and width
Figure 5-60. Click-drag from the green view to the Action One button and set the vertical spacing. This forces the green view to sit against the row of buttons .
Figure 5-61. Click-drag from Action One to Action Two, setting the horizontal spacing. Repeat for Action Two to Action Three as well as for Action Three to Action Four.
Figure 5-62. Click the Done Varying button to end the modifications for this set of traits
I hope you were able to follow this abbreviated form of using Auto Layout . If you found any issues, the best thing is to go back, delete your project, and start at the last baseline project. Until you work with Auto Layout for a dozen or so times, you’ll most likely make several mistakes and get frustrated. You’re not alone. When I’m away from Xcode and especially Auto Layout for a few weeks, I’m often walking away only to restart and re-layout things until I get it the way I need or want it to be.
Assuming you made it to this point successfully, click a few different iPad and orientation configurations to verify that things appear as you expect them, as shown in Figure 5-63.
Figure 5-63. Check to make sure your orientations appear correctly in the storyboard canvas
Finally, run the simulator for various devices making sure things appear as they should. Figure 5-64 shows what things should look like for an iPad Air in portrait and landscape orientations.
Figure 5-64. Verify things work as expected by running the simulator using various device types and checking orientations
Summary
In this chapter, I covered the basics of handling device rotations including getting heavily involved with using the new Xcode 8 Auto Layout and traits editor with the Device Configuration Bar. I started by talking the basics of rotations and what happens when you change orientation on an Apple device. The first project, Orientations, showed the basics of working with simple device rotations and maintaining positioning of labels. In the second project, Layout, you refined your knowledge of label positioning by putting labels into all four corners, as well as the left and right edges, to handle rotations.
Finally, in the Restructure project, you got very deep into understanding the use of Auto Layout for creating device and orientation-specific layout configurations. As you will be using Auto Layout for the rest of this book, as well as your career, make sure that you’re comfortable with its use before proceeding. While it can be very daunting at first, through practice, like with anything else, it will become second nature—until Apple changes things next year.