Auto Layout is a technique that can be used in Interface Builder as well as in code. You have created the contacts page entirely in Interface Builder, and that's very convenient. However, there are times when you might not be able to create the layout before the app runs. Let's call the moment of designing in Interface Builder the design time. At design time, it's possible that not all variables in your design are known. This means that you will need to dynamically add, remove, or update constraints when the app is running. When you do something while the app is running, you do it at runtime.
Setting up a layout visually with Auto Layout is very well supported, and Interface Builder even has some tools that make setting constraints programmatically fairly straightforward. If you have a very dynamic layout, you don't always know every constraint in advance. Especially if you are dealing with unpredictable data from external sources, a layout might need to change dynamically based on the available contents.
To help you with this, Interface Builder allows you to mark constraints as placeholder constraints in the Attributes Inspector. Marking a constraint as a placeholder means that it was used to create a valid layout for Interface Builder at design time, but you'll replace that constraint at runtime.
When you use Auto Layout in code, you can dynamically update constraints by changing the constant for a constraint at runtime. It's also possible to activate or deactivate certain constraints and to add or remove them. It doesn't matter if you created the constraints that you want to modify in code or Interface Builder. Any constraints that affect a view are accessible through code.
The best way to explore this is to get started with some coding. For the sake of this exercise, you will recreate the top part of the contact details page in code. This means you're going to set the constraints affecting the contact image and the contact name label as placeholder constraints in Interface Builder. The constraints for these views will then be added to the view controller, and you'll toggle some constraints' active states based on the current size class. The final code will also watch for changes in the size class and update the constraints accordingly if needed.
To get started, open ContactDetailViewController.swift and add the following two outlets to the class:
@IBOutlet var contactImage: UIImageView! @IBOutlet var contactNameLabel: UILabel!
Now, open the Main.storyboard file, select your view controller, and connect the outlets in the Connections Inspector to the correct views. After you have connected the outlets, you should change the existing constraints that position the image and label so they become placeholder constraints instead of regular constraints. The constraints that need to be changed are the following:
- Width constraints for the image (both regular and compact)
- Height constraints for the image (both regular and compact)
- Top spacing constraint for the image
- Horizontal center constraint for the image
- Spacing constraint between the label and the image
- Horizontal center constraint for the label
The vertical spacing constraint between the label and the bottom views should remain intact. To change a constraint to a placeholder, you must select it and check the placeholder checkbox in the Attributes Inspector. If you build and run your app after doing this, you'll end up with a mostly white view. That's okay; you have just removed some essential constraints that need to be re-added in code.
To get everything up and running again, you'll write some code to implement the eight constraints that were removed. One of the tools you can use to do so is Visual Format Language (VFL). This language is an expressive, declarative way to describe Auto Layout constraints. You can describe multiple constraints that affect multiple views at once, which makes this language very powerful.
A visual format string contains the following information:
- Information regarding the axis on which the constraint should work: This is either horizontal (H), vertical (V), or not specified. The default is horizontal.
- Leading space to superview: This is optional.
- Affected view: This is required.
- Connection to another view: This is optional.
- Trailing space to superview: This is optional.
An example of a visual format string looks like this:
V:|-[contactImageView(60)]-[contactNameLabel]
If you take this string apart piece by piece, it contains the following information:
- V:: This specifies that this format string applies to the vertical axis.
- |: This represents the superview.
- -: This applies a standard spacing of about 8 points. This is equivalent to spacing a view in Interface Builder using the blue guidelines.
- [contactImageView(60)]: This places the contactImageView and gives it a height of 60 points. The placement will be about 8 points from the top of the superview.
- -: This applies another standard spacing.
- [contactNameLabel]: This places contactNameLabel with about 8 points of spacing from contactImageView.
This way of describing layouts takes some getting used to, but it's a really powerful way to describe layouts. Once you get the hang of all definitions and possibilities, you'll find that a visual format string is a very descriptive representation of the layout you're trying to create.
Time to dive right in and take a look at how to implement the entire layout for the top section of the contact details page. You'll only create the constraints for the compact size class for now and you'll add the regular constraints later.