Auto Layout Visual Format Language Tutorial
In this tutorial you will learn how to use the Auto Layout Visual Format Language to easily lay out your app’s user interface using code. By József Vesza.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Auto Layout Visual Format Language Tutorial
25 mins
The Auto Layout Visual Format Language (VFL) allows you to define constraints by using an ASCII-art formatted string.
With a single line of code, you can specify multiple constraints in either the horizontal or vertical direction. This can save a lot of code compared to creating constraints one at a time.
In this tutorial, you and VFL will become buddies as you do the following:
- Construct horizontal and vertical constraints
- Use
views
definitions inside your VFL string - Use
metrics
constants inside your VFL string - Use layout options to position interface elements relative to others
- Use safe area to take the iPhone X into consideration
Note: this tutorial assumes you’re well acquainted with Auto Layout. If you’re fairly new to it, you may want to start with Auto Layout Tutorial in iOS 11: Getting Started.
Note: this tutorial assumes you’re well acquainted with Auto Layout. If you’re fairly new to it, you may want to start with Auto Layout Tutorial in iOS 11: Getting Started.
Getting Started
Start by downloading the starter project for this tutorial, which comprises a basic welcome screen for a fledgling social networking app — Grapevine. Build and run (Product \ Run or ⌘R) the project in Xcode; you’ll see the following (to rotate the simulator go to Hardware \ Rotate Right):
Well, that’s a hot mess. Why is this happening and what are you going to do about it?
All the interface elements are currently pinned to the top and left of the view, and this is the result of them having no associated Auto Layout constraints. You’ll make the view much prettier during the course of this tutorial.
Open Main.storyboard and look at the interface elements. Note the interface elements are set with Auto Layout constraints that are removed at compile time. You wouldn’t do this in a real project, but this saves you having to enter a lot of view creation code :]
Next, open ViewController.swift and have a look inside. At the top, you’ll see outlets connected to the Interface Builder (IB) interface elements inside Main.storyboard.
There’s not much else to talk about in the app at this point, but there’s a lot good stuff to learn about VFL!
Visual Format String Grammar
Before you dive into setting up layouts and constraints, you’ll need some background knowledge on the VFL format string.
First thing to know: The format string can be split into the following components:
Here’s a step-by-step explanation of the VFL format string:
-
Direction of your constraints, not required. Can have the following values:
- H: indicates horizontal orientation.
- V: indicates vertical orientation.
- Not specified: Auto Layout defaults to horizontal orientation.
-
Leading connection to the superview, not required.
- Spacing between the top edge of your view and its superview’s top edge (vertical)
- Spacing between the leading edge of your view and its superview’s leading edge (horizontal)
- View you’re laying out, is required.
- Connection to another view, not required.
-
Trailing connection to the superview, not required.
- Spacing between the bottom edge of your view and its superview’s bottom edge (vertical)
- Spacing between the trailing edge of your view and its superview’s trailing edge (horizontal)
There’s two special (orange) characters in the image and their definition is below:
- ? component is not required inside the layout string.
- * component may appear 0 or more times inside the layout string.
Available Symbols
VFL uses a number of symbols to describe your layout:
-
|
superview -
-
standard spacing (usually 8 points; value can be changed if it is the spacing to the edge of a superview) -
==
equal widths (can be omitted) -
-20-
non standard spacing (20 points) -
<=
less than or equal to -
>=
greater than or equal to -
@250
priority of the constraint; can have any value between 0 and 1000- 250 - low priority
- 750 - high priority
- 1000 - required priority
Example Format String
H:|-[icon(==iconDate)]-20-[iconLabel(120@250)]-20@750-[iconDate(>=50)]-|
Here's a step-by-step explanation of this string:
-
H:
horizontal direction. -
|-[icon
icon's leading edge should have standard distance from its superview's leading edge. -
==iconDate
icon's width should be equal to iconDate's width. -
]-20-[iconLabel
icon's trailing edge should be 20 points from iconLabel's leading edge. -
[iconLabel(120@250)]
iconLabel should have a width of 120 points. The priority is set to low, and Auto Layout can break this constraint if a conflict arises. -
-20@750-
iconLabel's trailing edge should be 20 points from iconDate's leading edge. The priority is set to high, so Auto Layout shouldn't break this constraint if there's a conflict. -
[iconDate(>=50)]
iconDate's width should be greater than or equal to 50 points. -
-|
iconDate's trailing edge should have standard distance from its superview's trailing edge.
Now you have a basic understanding of the VFL -- and more importantly the format string -- it's time to put that knowledge to use.
Creating Constraints
Apple provides the class method constraints(withVisualFormat:options:metrics:views:)
on NSLayoutConstraint
to create constraints. You'll use this to create constraints programmatically inside of the Grapevine app.
Open ViewController.swift in Xcode, and add the following code:
override func viewDidLoad() {
super.viewDidLoad()
appImageView.isHidden = true
welcomeLabel.isHidden = true
summaryLabel.isHidden = true
pageControl.isHidden = true
}
This code hides all interface elements except the iconImageView
, appNameLabel
and skipButton
. Build and run your project; you should see the following:
Cool. You've cleared the cluttery interface elements, now add the following code to the bottom of viewDidLoad()
:
// 1
let views: [String: Any] = [
"iconImageView": iconImageView,
"appNameLabel": appNameLabel,
"skipButton": skipButton]
// 2
var allConstraints: [NSLayoutConstraint] = []
// 3
let iconVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:|-20-[iconImageView(30)]",
metrics: nil,
views: views)
allConstraints += iconVerticalConstraints
// 4
let nameLabelVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:|-23-[appNameLabel]",
metrics: nil,
views: views)
allConstraints += nameLabelVerticalConstraints
// 5
let skipButtonVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:|-20-[skipButton]",
metrics: nil,
views: views)
allConstraints += skipButtonVerticalConstraints
// 6
let topRowHorizontalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "H:|-15-[iconImageView(30)]-[appNameLabel]-[skipButton]-15-|",
metrics: nil,
views: views)
allConstraints += topRowHorizontalConstraints
// 7
NSLayoutConstraint.activate(allConstraints)
Here's a step-by-step explanation of the above code:
-
Create a
views
dictionary that holds string representations of views to resolve inside the format string. - Create a mutable array of constraints. You'll build this up in the rest of the code.
-
Set up vertical constraints for the
iconImageView
, placing its top edge 20 points from its superview's top edge, with a height of 30 points. -
Set up vertical constraints for the
appNameLabel
, placing its top edge 23 points from its superview's top edge. -
Set up vertical constraints for the
skipButton
, placing its top edge 20 points from its superview's top edge. -
Set up horizontal constraints for all three interface elements. The
iconImageView
's leading edge is placed 15 points from the leading edge of its superview, with a width of 30 points. Next, a standard spacing of 8 points is placed between theiconImageView
andappNameLabel
. Next, a standard spacing of 8 points is placed between theappNameLabel
andskipButton
. Finally, theskipButton
's trailing edge is placed 15 points from the trailing edge of its superview. -
Activate the layout constraints using the class method
activate(_:)
onNSLayoutConstraint
. You pass in theallConstraints
array you've been adding to all this time.
Note: The string keys inside the views dictionary must match the view strings inside the format string. If they don't, Auto Layout won't be able to resolve the reference and will crash at runtime.
Note: The string keys inside the views dictionary must match the view strings inside the format string. If they don't, Auto Layout won't be able to resolve the reference and will crash at runtime.
Build and run your project. How do the interface elements look now?
Hey, look at that! You've already made it prettier.
Now stick with it here, that first part was just a teaser. You've got a lot of code to write, but it'll be worth it at the end.
Next, you'll lay out the remaining interface elements. First, you need to remove the code you originally added to viewDidLoad()
. I know, I know...you just put it there. Delete the following lines:
appImageView.isHidden = true
welcomeLabel.isHidden = true
summaryLabel.isHidden = true
pageControl.isHidden = true
Removing this reverts the display so it shows the remaining interface elements that you previously hid from yourself.
Next, replace your current views
dictionary definition with the following:
let views: [String: Any] = [
"iconImageView": iconImageView,
"appNameLabel": appNameLabel,
"skipButton": skipButton,
"appImageView": appImageView,
"welcomeLabel": welcomeLabel,
"summaryLabel": summaryLabel,
"pageControl": pageControl]
Here you've added view definitions for the appImageView
, welcomeLabel
, summaryLabel
and pageControl
, which can now be used inside the VFL format string.
Add the following to the bottom of viewDidLoad()
, above the activate(_:)
call:
// 1
let summaryHorizontalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "H:|-15-[summaryLabel]-15-|",
metrics: nil,
views: views)
allConstraints += summaryHorizontalConstraints
let welcomeHorizontalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "H:|-15-[welcomeLabel]-15-|",
metrics: nil,
views: views)
allConstraints += welcomeHorizontalConstraints
// 2
let iconToImageVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[iconImageView]-10-[appImageView]",
metrics: nil,
views: views)
allConstraints += iconToImageVerticalConstraints
// 3
let imageToWelcomeVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[appImageView]-10-[welcomeLabel]",
metrics: nil,
views: views)
allConstraints += imageToWelcomeVerticalConstraints
// 4
let summaryLabelVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[welcomeLabel]-4-[summaryLabel]",
metrics: nil,
views: views)
allConstraints += summaryLabelVerticalConstraints
// 5
let summaryToPageVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[summaryLabel]-15-[pageControl(9)]-15-|",
metrics: nil,
views: views)
allConstraints += summaryToPageVerticalConstraints
Here's a step-by-step explanation of the above:
-
Set up horizontal constraints for the
summaryLabel
andwelcomeLabel
, placing them 15 points from the leading and trailing edges of their superview. - Set up vertical constraints for the icon to the app image, with spacing of 10 points
- Set up vertical constraints for the app image to the welcome label, with spacing of 10 points
- Set up vertical constraints between the welcome label and summary label, with a spacing of 4 points
- Set up vertical constraints between the summary label and the page control, with a spacing of 15 points and a height of 9 points for the page control, then spacing of 15 points to the superview
Build and run your project; how do the interface elements look?
Now you're getting somewhere. No, it's not exactly what you're looking for; some interface elements are laid out correctly, however, others are not. The image and the page control aren't centered.
Never fear, the next section will provide you with more ammunition to get the layout to clean up its act.