Bummer! This is just a preview. You need to be signed in with a Basic account to view the entire video.
Start a free Basic trial
to watch this video
In this recipe we're going to build a two view layout where each view occupies a varying width depending on the available space.
Concept Videos
Cookbook Videos
-
0:00
In this recipe, we're going to build a two view layout,
-
0:03
where each view occupies a varying width depending on the available space.
-
0:08
We'll have two views, a red and a blue view,
-
0:12
where we want the red view to be double the width of the blue view if possible.
-
0:17
The catch is that the blue view can't be too narrow either.
-
0:21
And we want to enforce a minimum width.
-
0:24
We'll need to use a combination of inequality relations and
-
0:28
constrained priorities to build this relationship.
-
0:31
I've gone ahead and added a file to my project named ComplexWidthViews.
-
0:36
And in here I have a custom UIViewController subclass.
-
0:41
So inside this class, we have some starter code that defines a blue and
-
0:46
a redView, and that's really all I've done here.
-
0:49
In viewDidLoad, I've set these views to have the right background colors.
-
0:53
And then the first thing we always want to do when setting constraints in code
-
0:56
is to disable autoresizing masks.
-
1:00
This is handled automatically in interface builder,
-
1:02
but it's important to be aware of this in code.
-
1:05
Otherwise you'll end up with constraints you didn't add,
-
1:08
which will break your layout.
-
1:10
So I've added that inside viewWillLayoutSubviews, and
-
1:12
then I've added the views as subviews of the root view.
-
1:17
So ensure you do this step every single time
-
1:20
before you activate any constraints in code.
-
1:24
Now for this layout, we won't be doing anything special in the vertical axis, so
-
1:28
let's get some basic constraints in place.
-
1:31
We'll send vertical spacing constraints of eight points to the top and bottom for
-
1:35
both the views.
-
1:37
I'm building this layout in XCode 9 against the iOS 11 SDK,
-
1:41
which means I can make use of the new safe area layout guide APIs.
-
1:46
But we also need to ensure that this builds for versions prior to iOS 11.
-
1:50
So we're going to use the if available syntax to target all the different cases.
-
1:55
We'll start by saying if #available, so if #available,
-
2:00
and the platform is iOS and the version is 11.
-
2:04
And if we're in version 11 of iOS,
-
2:06
then we'll use as a guide the view's safeAreaLayoutGuide.
-
2:13
Otherwise we'll execute some code in the else case.
-
2:18
In the iOS 11 case, we can use the view.safeAreaLayoutGuides,
-
2:22
like I mentioned.
-
2:23
But in any other case, prior to iOS 11, we'll fall back to the standard top and
-
2:28
bottom layout guides or margins.
-
2:31
The first set of constraints affected by this are the top and
-
2:33
bottom constraints on the blue view, so let's add those in.
-
2:37
We'll use the NSLayoutConstraint class, and
-
2:40
we'll call a class method activate that takes a series of constraints.
-
2:45
So we'll say blueView.topAnchor.constraint(equalTo:
-
2:52
guide.topAnchor, constant: 8.0).
-
2:59
We also want to add a similar constraint to the bottom anchor to space it out,
-
3:05
so constraint(equalTo: guide.bottomAnchor, constant: -8.0).
-
3:11
This time, making sure it's negative to take into account the direction.
-
3:16
Okay, so this is the case for the safeAreaLayoutGuides.
-
3:19
In the else case, we'll create another NSLayoutConstraint,
-
3:23
where we'll call the activate method here.
-
3:26
And this time, so we'll copy this constraint over, so blueView.topAnchor.
-
3:32
But instead of going to the guide's top anchor,
-
3:35
here we'll say topLayoutGuide.bottomAnchor.
-
3:39
And then we'll copy paste the first one, or rather the second one,
-
3:43
the bottom anchor constraint.
-
3:45
And here we'll say bottomLayoutGuide.topAnchor.
-
3:52
Okay, remember we're doing an 8 point vertical space constraint
-
3:56
on the redView as well, so we need to mimic these.
-
3:59
And we can simply just copy these over, I'll leave data, in here.
-
4:04
And then we'll paste, and here, we'll say redView for each of these.
-
4:11
And that should be it.
-
4:12
And so we can do the same right here.
-
4:16
Copy, paste, and we will change this to redView.
-
4:22
That takes care of the vertical constraints.
-
4:25
In the horizontal axis, regardless of the width of the views,
-
4:29
we're going to space them 8 points on either side.
-
4:32
So let's add those constraints in before we define any of the width constraints.
-
4:38
In iOS 11 and above,
-
4:39
we'll add these constraints from the subviews to the safe area.
-
4:43
While in versions prior to iOS 11, we'll add them to the super view.
-
4:47
So I will put that right here and we'll say blueView.leadingAnchor.
-
4:52
You can do left anchor because there's no content in here really, but
-
4:55
we'll just go ahead with the leadingAnchor.
-
4:57
And we'll say constraint(equalTo:
-
5:01
guide.leadingAnchor, constant: 8.0).
-
5:06
Then we'll do redView.trailingAnchor.constraint(equalTo:
-
5:14
guide.trailingAnchor, constant: -8.0).
-
5:20
You'll notice that I'm not adding the blueView's trailing constraint here.
-
5:23
And that's because safe area doesn't matter there.
-
5:26
Now we're going to add that to the red view, so
-
5:28
we can do it outside this if else clause.
-
5:31
Okay, we need to do the same thing at the bottom, so add a comma.
-
5:34
And we'll say blueView.leadingAnchor.constraint(equalTo:
-
5:41
view.trailingAnchor, right?
-
5:44
There's no layout guide here, so we're adding directly to the root view.
-
5:49
With a constant value of 8 points, and then finally
-
5:54
redView.trailingAnchor.constraint(equalTo: view,
-
6:00
oops, view.leading for the blue.
-
6:04
Make sure you correct that as well, and then view.trailing for the red.
-
6:08
The constant value here is -8.0.
-
6:12
We have one horizontal space constraint to add between the two views.
-
6:16
But again, since this does not depend on either the safe area layout guides or
-
6:20
any traditional layout guides,
-
6:22
we can add it to the hierarchy outside the if else block.
-
6:25
So NSLayoutConstraint.activate, and
-
6:29
this constraint is blueView.trailingAnchor.constraint(equa-
-
6:35
lTo: redView.leadingAnchor, constant: -8.0).
-
6:40
With these spacing constraints out of the way, let's focus on our width constraints.
-
6:45
We'll start by setting a with constraint on the blueView.
-
6:49
Remember when we said that while we want the blueView to adjust its size to
-
6:53
accommodate a growing redView, we don't want it to get too narrow.
-
6:56
So we're going to enforce a minimum width constraint by setting a greater than or
-
7:01
equal relation.
-
7:02
So we'll say blueView.widthAnchor.constraint, now make
-
7:07
sure you pick greater than or equal to constant here, and we'll say 150 points.
-
7:15
Now this constraint alone is insufficient information.
-
7:19
Now it's hard to kind of imagine this, but the blueView,
-
7:22
since it's allowed to grow more than 150 points, it could take up nearly all of
-
7:27
the available space, shrinking the red view down to a 1 point width.
-
7:31
And technically our layout would be satisfied.
-
7:34
It could also fit a wide range of widths between where we're at now, so let's say
-
7:40
the blue view is 150, and up to where the width allows for a 1 point wide red view.
-
7:46
So that's not what we want, so we need to add more constraints.
-
7:49
Unfortunately this is hard to visualize, like I've said.
-
7:51
If you've taken the interface builder version of this video,
-
7:54
you can see that visually.
-
7:56
But here we just kind of have to imagine.
-
7:58
To fix this, we need to define some constraints
-
8:01
that allows auto layout to determine a width for the red view.
-
8:05
With a width for the redView, we can then figure out what the width for
-
8:08
the blueView needs to be.
-
8:10
In this particular recipe,
-
8:12
we want the redView to be twice as wide as the blueView if possible.
-
8:16
I say if possible because we can only do that up to a certain limit.
-
8:21
In the portrait orientation, with the minimum width constraint,
-
8:24
there's not enough room to accommodate a redView that's twice as wide.
-
8:28
But that's okay, in that case,
-
8:30
we would want the redView to just fill up the remaining space.
-
8:33
But in the case that there is available space to grow up to twice as wide,
-
8:38
then we want it to do so.
-
8:40
This seems complicated, but it can be achieved with one constraint.
-
8:45
And instead of defining this constraint inside the array directly like we've been
-
8:49
doing, I'm going to assign it to a local constant, so we'll do it up here.
-
8:52
So we'll say, let redViewWidthConstraint =
-
8:57
redView.widthAnchor.constraint(equalTo:.
-
9:03
Now we're not going to select the constant one, we're going to look for
-
9:06
the method that takes a multiplier.
-
9:08
So we'll say equalTo: blueView.widthAnchor.
-
9:13
And the multiplier is 2.0, so we want it to be twice as wide as the blueView.
-
9:19
Let's add this constraint to our array of activated constraints, so
-
9:23
comma redViewWidthConstraint.
-
9:25
If you run the project now, the layout will still be broken.
-
9:30
And that's because with our new constraint, we're violating the minimum
-
9:33
width constraint on the blue view, at least in portrait mode.
-
9:37
In portrait mode, we cannot have a situation where the blue view has
-
9:41
a minimum width of 150 points and where the red view is twice that or 300 points.
-
9:47
There's just not enough space.
-
9:49
So the layout is unresolved.
-
9:52
To fix this, the last adjustment we need to make is to drop the priority
-
9:56
on this redViewWidthConstraint to 750, or
-
10:00
to make it high in the default or the predefined values.
-
10:04
This is why we assigned the constraint to a local constant.
-
10:07
We can only change priorities on a constraint after it has been created.
-
10:11
So we'll say redViewWidthConstraint.priority.
-
10:15
Now you can simply say 750 here.
-
10:18
But that's not what we want.
-
10:19
Well, we do want that, but there's an easier way.
-
10:21
We can simply say .defaultHigh.
-
10:24
So this is actually a type UILayoutPriority.
-
10:29
Click here, so we can go see it.
-
10:31
So jump to definition, priority, and
-
10:33
you'll see that UILayoutPriority is a struct.
-
10:38
And you can initialize it with a float,
-
10:41
which is why we were able to assign 750 directly to it.
-
10:45
But it also has these static properties required, defaultHigh,
-
10:47
defaultLow, and so on.
-
10:48
So we're saying defaultHigh, that avoids us being magic numbery.
-
10:54
By lowering the priority on the proportional width constraint,
-
10:57
we've effectively made it act like a less than or equal to constraint.
-
11:02
Since the greater than or equal constraint has a higher priority, auto layout will
-
11:07
first make sure that a minimum width of 150 points is enforced for the blue view.
-
11:13
Having a lower priority than 1000, the proportional width
-
11:17
constraint in a scenario of conflict is treated as optional.
-
11:21
Which means that along with the horizontal spacing constraints on either side,
-
11:26
auto layout will simply set the red view's width as the remaining available space.
-
11:31
This means that technically the red view can be any width
-
11:34
less than double the blue view's width and at most the blue view's width,
-
11:39
which is essentially what a less than or equal relation would define.
-
11:43
It is important to note however that while it might seem that way,
-
11:48
auto layout does not view it as a less than or equal relationship.
-
11:53
If we were to modify this proportional width constraint that we've defined here
-
11:57
to have a less than or
-
11:58
equal relation with the priority of 1,000, the layout will not be satisfied.
-
12:03
So the key here is that for our layout to work, the lower priority allows auto
-
12:08
layout to ignore the constraint when it cannot accommodate it.
-
12:12
Let us see what this looks like.
-
12:14
Head over to Main.storyboard, select the view controller in here.
-
12:19
And in the backing class in the identity inspector,
-
12:22
we'll switch it to what we just wrote, which is ComplexWidthController.
-
12:28
And let's run it.
-
12:30
So you can see here, in the portrait mode,
-
12:33
we're enforcing the minimum width constraint on the blue view.
-
12:37
So there's not enough space here for the red view to be twice as wide, so
-
12:41
we've set the blue view to 150 points.
-
12:43
And then the red view simply fills up whatever available space is left.
-
12:48
Now, if we switch to landscape orientation,
-
12:50
we can see how these constraints adapt.
-
12:53
In this view we see that the red view is double the width of the blue view.
-
12:57
But what's interesting, and you can't really measure it here, but
-
13:00
what is interesting is that the blue view is wider, as well.
-
13:04
Since the blue view has a greater than or
-
13:06
equal constraint in this orientation, there's enough room for
-
13:10
it to grow after the minimum width constraint has been satisfied.
-
13:15
Effectively what this allows auto layout to do is to take the available space
-
13:19
after the horizontal spaces between the views have been deducted and then allocate
-
13:24
two-thirds of that space to the red view and then one-third to the blue view.
-
13:28
When it does this, it allows the red view to be double the width of the blue view.
-
13:33
And after doing this, since there's more than 150 points of space available
-
13:38
in that one-third allocation, the blue view can grow to fit this space
-
13:42
since its width is defined using a greater than or equal constraint.
-
13:47
So here we have a complex width scenario
-
13:49
that was achieved with two constraints in the horizontal direction.
-
13:53
If you're interested in learning how to do this in interface builder,
-
13:57
check the notes section for a link to this video.
You need to sign up for Treehouse in order to download course files.
Sign up