As I mentioned in my last post, last week I did a talk at iOSDevCamp DC where I talked about
UIStackView, a relatively new UIKit class that's my new favorite thing in iOS development. I'm going to cover some of the more useful things
UIStackView can do in posts here, which will fall more or less into two categories:
- Simplified and flexible UI design, making common UI patterns easier to implement and modify.
- Dynamic UI updates when apps are running, including UI animations and updates triggered by size class changes.
Today I'll hit point #1 above. The cool, useful thing is this:
Stack views make (most of) your autolayout constraints unnecessary,
for common linear UI layouts.
Side by Side Comparison
So let's take a look at what
UIStackView can do at the design stage. I implemented a simple demo app that displays a map view in the top part of the screen with location-related details below.
In order to make it interesting and provide a literal side-by-side comparison, I used a tab view controller and implemented the same UI in both tabs. In the left tab I added autolayout constraints to put everything in its place and make sure it stays there. The tab on the right uses nested
UIStackView containers, and as a result needs far fewer explicit layout constraints. Other than that the tabs look the same, and even use the same view controller class.
If you're new to
UIStackView, keep in mind that both versions use autolayout to position the UI elements, and get all of the advantages that come with that. Both versions respond well to device rotation, both work well with different screen sizes, etc. The difference is that in one case I created the constraints, while in the other I configured a
UIStackView and let it work out what constraints were needed.
So how does that work out?
Here's what the constraints and view hierarchy look like when not using stack views.
It's not extremely complicated. Each view has constraints that specify both position and size. The layout works just as well for different screen sizes or when the device rotates. The only detail that required any thought was working out how to constrain the text fields to be almost but not quite half as wide as their containing view.
Here's the same thing with the alternate implementation, which uses nested stack views for layout:
There's a vertical stack view that fills the screen. Within that there's a horizontal stack view to hold the two side-by-side columns of UI elements at the bottom of the screen. And within those, there's another level of vertical stack views containing the vertically arranged text fields and labels.
None of the UI elements in the bottom part of the screen have any explicit constraints. They're still constrained, of course, but the stack view adds the constraints automatically. Everything works exactly as in the previous version. Of the constraints that do exist, half of them are just to make sure that the top level stack view fills the screen.
There's nothing wrong with adding your own constraints, and in some cases it's still necessary. But I've always followed the rule that the best code is the code you didn't have to write, and the same thing applies to layout constraints. If I can get results that are just as good with far fewer constraints, I know which strategy I'll use.
Of course the view hierarchy gets more complex in the stack view version. Instead of one containing view and a handful of subviews, it has several nested stack view levels. I maintain that this is strongly outweighed by the advantages. The extra nesting isn't as complex as it might seem at first, because it reflects the UI organization. The stack views reflect the logical, conceptual grouping of the views. They therefore reflect the mental model that the UI implies.
If the above wasn't enough to convince you of this, read on.
When Requirements Attack. Change! When Requirements Change!
The UI above shows four details of the user's current location, the latitude/longitude coordinate, the altitude, and the horizontal accuracy. Core Location also tells us the user's current speed and direction. What if we want to add those? And what if we want to put the new information in the middle of the existing fields?
Using traditional artisanally hand-crafted layout constraints, the process is something like:
- Move several existing views
- Add the new views
- Remove or adjust a bunch of constraints
- Add a bunch of new constraints
Let's see what that looks like in this case.
Again, it's not difficult, but it's a lot of tedious and error prone steps. But compare that to doing the same thing with a
UIStackView based layout. The process is:
- Add the new views
That was... a whole lot easier. For results that work just as well. That was worth a bit of extra complexity in the view hierarchy.
Because of a limitation in Xcode, there's one step in that video that might not be clear. The first thing I did was to change the alignment of a horizontal stack view from "fill" to "top". Why? Because I'm about to change the heights of the views it contains, and I can't change them at the same time. If I change one, then the other, the results work with "fill" but Xcode gets confused and misaligns UI elements that are in the right place when the app runs. Changing the alingment to "top" makes Xcode happy and doesn't affect the final result.
After making Xcode happy, though. Wow. Add the new views and... I'm done.
UIStackView gives me the constraints I need, and I go on to something else.
Update: Where's the Code?
After getting requests on Twitter, I've uploaded the Xcode project used for this post to GitHub. The tag that corresponds to this post's demos is the "blog-1" tag.
That's not enough? OK, how about if I told you that stack views can also make it easy to make your UI highly responsive to device rotation and to add useful and cool animations to your user interface? Natasha the Robot covered some of this in her posts based on my iOSDevCamp DC talk. I'll be covering more of it in upcoming posts.