What’s more important when troubleshooting software, 1) what you intended in design or 2) what was materialized by running your code in a production environment? Take Auto Layout for example. Interface Builder may be happy with your constraints, displaying no warnings or errors, but when you run your app, you see problems. I find it much more helpful to see my all my Auto Layout live, while my app is running. I’ve found that using Xcode’s Debug View Hierarchy button is an often over-looked but extremely powerful tool for solving app layout problems, especially when iOS developers have to write app user interfaces that run on differently-sized devices in multiple orientations. The Debug View Hierarchy feature helps you understand how Auto Layout works. You can see all of your app’s:
- user interface (UIKit) components (e.g., UIWindow, UIView, UIButton, UILabel, UIImageview…) as they appear in your scenes, all together or in pieces;
- Auto Layout constraints with details (e.g., equations, UIView bounds, UIImageView Mode [like Aspect Fit], UIView background color…);
- user interface components and their constraints in an expandable hierarchical list;
- layering of “parent, child, and sibling views in the view hierarchy” (and you can see all this in 3D).
Check out this screenshot of a view debugging session in Xcode (click to enlarge):
What do you do first when you find a problem while testing your app on different sized devices to ensure that your Auto Layout constraints were set up properly? Do you go straight to Interface Builder and look at the constraints set in your storyboards and/or XIBs? Do you go straight into code to inspect all your programmatically created constraints (NSLayoutAnchor, NSLayoutConstraint, Visual Format Language)? That’s one approach…
Let’s walk through solving a problem using the Debug View Hierarchy button. Understand that I am presenting a simplified version of my original problem for the purposes of making this tutorial concise.
Using Xcode 7.3, I created a UIViewController scene in my storyboard and set up my Auto Layout constraints. Xcode 7.3 gave me no Auto Layout warnings or errors. Note that Xcode 8.2.1 is now smart enough to warn me about the problems I’m describing herein, but I’m showing you a bonafide problem I had to solve, so this example is instructive. Also note that the UIViewController in this example was designed to be displayed only in landscape mode on an iPad. I specified the original Auto Layout almost entirely using visual tools, i.e., 1) [control]-clicking and dragging to get the constraint context menu and 2) using the Pin button. Note that, for some unknown reason, I set the width of the UIImageView twice. But that’s good in this case as I’m highlighting the fact that I introduced a bug and needed to fix it. Here is my original layout (click to enlarge):
Here’s what happened. I’ve done most of my app testing on an iPad Mini and iPad 3. I just recently purchased an iPad Pro. During testing, my scene always looked great on the iPad Mini and iPad 3. But I found that my layout looked awful on on iPad Pro. See the following three images:
When I saw that big fat UIToolBar, I knew there was a problem. It just looks awful — and is a waste a good screen real estate. I want users to see that UIImageView as large as possible and to take advantage of the iPad Pro’s large Retina screen. So I ran my app from Xcode on my iPad Pro, went to the Debug Area, and clicked the Debug View Hierarchy button (click to enlarge):
Just scanning down the View Hierarchy (left pane) I almost immediately saw my problem. I was on the right track when I originally designed the scene, adding “Aspect Ratio 1:1,” “Width,” and “Height” constraints on my UIImageView, but I over-constrained the image, limiting its width and height to no more than 660 points. The iPad Pro could’ve displayed a larger version of my image, but I told it not to. So the iOS Auto Layout runtime compensated by stretching the height of the top UIToolbar.
I almost immediately knew where to get started in fixing the small UIImageView/fat UIToolbar problem. I went into my storyboard scene and made the following Auto Layout changes to my UIImageView, considering my original constraints in Figure 3 above:
- deleted one of the “width = 660” constraints;
- deleted the “height = 660” constraint; and,
- changed the remaining “width = 660” constraint to “width ≥ 660”.
So now the UIImageView (which I’ve since renamed “Big Image” in the Auto Layout Document Outline) constraints look like this:
I ran my app on my iPad Mini, iPad 3, and iPad Pro. The UIImageView was now using available screen real estate, and the gray view to the right of it was fine… But the the top and bottom toolbars were now missing! What happened?
Remember that I just added the constraint “width ≥ 660”. Xcode assigned that constraint a Priority of “Required (1000)”. My guess is that since I only Pinned the top and bottom UIToolbars, and didn’t explicitly constrain their heights, they got squeezed into oblivion. I find the UIToolbar to be a bit confusing in terms of its Intrinsic Content Size. Whenever I drag a UIToolbar from the Object Library onto a scene in the storyboard, its Height is always displayed as an non-editable 44 points — look at my top UIToolbar in the Size Inspector:
Also note that in Figure 7, the Debug View Hierarchy reports that the top UIToolbar has constraint “toolbar.height = 44 (content size)” yet it obviously is much larger than 44 points in height. Huh, Apple?
The more I think about it, the more it makes at least a little sense. The UIToolbar is a descendent of the UIView and the UIView has “No intrinsic content size” according to Apple. (That still doesn’t explain the UIToolbar’s non-editable 44 point Height.) Anyway…
To get the top and bottom UIToolbars back into my scene, I had to add the following constraints/properties to each:
- height = 44
- Content Compression Resistance Priority -> Vertical = “Required (1000)”
Adding “height = 44” wasn’t enough. I was only able to get the UIToolbars back onto my scene by setting the Content Compression Resistance Priority.
“Compression Resistance” is just what it says: It can keep a view from getting compressed (squeezed/shrunk) — but allows you to fine-tune how much resistance you want by setting the Horizontal or Vertical Priority. For more information, please click here.
Problem solved! My landscape-only scene now displays properly on my iPad Mini, iPad 3, and iPad Pro. Here’s a look at my updated app running in Xcode’s Debug View Hierarchy tool. Note that I’ve highlighted some important features:
Make sure to take full advantage of the Debug View Hierarchy tool:
- You can expand out all nodes in the View Hierarchy (left pane) down to constraint values. Clicking on UIView object(s) and/or constraint(s) in the left pane will visually highlight the respective element(s) in the middle pane;
- Clicking on components of interest — above in Figure 10, the UIImageView — in the big middle window will visually highlight that component; and,
- Whether you click on a component in the left pane or middle pane, you can get all sorts of details about the component in the right pane, choosing the Object Inspector or Size Inspector.
For example, in Figure 10, I used the Size Inspector to:
- determine that the bounds of my UIImageView were (x,y,width,height) = (0,0,916,916);
- find out which Auto Layout constraints were active — being actually used by the runtime (the top 4 equations under the heading “Auto Layout;” the ones in a normal font);
- find out which Auto Layout constraints were inactive, superseded by my explicit constraints, having a lower priority — being ignored by the runtime (the bottom 5 equations under the heading “Auto Layout;” the ones grayed out); and,
- any constraints created by the Auto Layout runtime (like the UIImageView’s “self.width = 639 (content size)” constraint.
Note: The “self.width = 639 (content size)” and “self.height = 639 (content size)” constraints are telling me the Intrinsic Content Size of the image I’m displaying in the UIImageView. This scene was designed so support the display of many different images. The UIImageView.image property is set at runtime. I threw a quick image together for this tutorial and stuck it into the 1x slot of an Image Set in the Asset Catalog. The size I picked for the image was arbitrary: 639 px X 639 px. I didn’t bother creating 2x or 3x image assets.
Note: While the Debug View Hierarchy tool is immensely powerful, I don’t yet fully trust all the information it provides. I can provide details. Just leave a comment if you have questions or feedback.
I hope y’all learned a lot today. Next time you’re running an iOS app in Xcode, try the Debug View Hierarchy tool, experiment, explore, learn — and practice, practice, practice.