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
Safe Area
The UI is starting to look great, but so far you've only tried it on the traditional, rectangle-shaped screens. In September 2017, Apple introduced a new device, which doesn't quite fit this description: the iPhone X. To see how Grapevine looks on this new device, start the app in the iPhone X simulator.
Well, it isn't the most pleasant sight. While the image, and the welcome text is mostly okay, you'll notice the UI interferes with system elements on both the top and the bottom of the screen. Luckily, with the use of safe area, you can easily work around this issue!
Introduced in iOS 11, safe area indicates the area in which apps can show their UI without interfering with any special elements defined by UIKit, like the status bar, or a tab bar. In case of the iPhone X, the safe area is different in portrait, and landscape mode:
You'll notice in portrait mode, there's more space on the top, and bottom. In landscape however, the left, and right insets are larger. So far you've put all your constraint-related code in viewDidLoad()
, but since the safe area may change during runtime, that'll no longer cut it. Luckily, view controllers will be notified by the viewSafeAreaInsetsDidChange()
on safe area changes, so you can start there.
Open, ViewController.swift and completely remove your viewDidLoad()
method. That's right, you read correctly; you'll re-implement this functionality in viewSafeAreaInsetsDidChange()
later.
Next, add the following property below your IBOutlet
definitions:
private var allConstraints: [NSLayoutConstraint] = []
This property will store all currently active constraints within the view controller so they can be deactivated and removed when new constraints are required.
Next, add the following implementation for viewSafeAreaInsetsDidChange()
:
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
if !allConstraints.isEmpty {
NSLayoutConstraint.deactivate(allConstraints)
allConstraints.removeAll()
}
let newInsets = view.safeAreaInsets
let leftMargin = newInsets.left > 0 ? newInsets.left : Metrics.padding
let rightMargin = newInsets.right > 0 ? newInsets.right : Metrics.padding
let topMargin = newInsets.top > 0 ? newInsets.top : Metrics.padding
let bottomMargin = newInsets.bottom > 0 ? newInsets.bottom : Metrics.padding
let metrics = [
"horizontalPadding": Metrics.padding,
"iconImageViewWidth": Metrics.iconImageViewWidth,
"topMargin": topMargin,
"bottomMargin": bottomMargin,
"leftMargin": leftMargin,
"rightMargin": rightMargin]
}
The code above will make sure you remove any previously activated constraints otherwise you'll get auto layout errors. It also extends the metrics dictionary with calculated margins. You can access the new insets by the safeAreaInsets
property of the view. In case of the rectangle-shaped phones, insets will be 0; the iPhone X however will have different values based on its orientation. To cater to both of these cases, you'll use the inset value if it's greater than zero, but will fall back to the padding defined earlier if it's not.
Finally, add the following constraints using the new metrics to the end of viewSafeAreaInsetsDidChange()
:
let views: [String: Any] = [
"iconImageView": iconImageView,
"appNameLabel": appNameLabel,
"skipButton": skipButton,
"appImageView": appImageView,
"welcomeLabel": welcomeLabel,
"summaryLabel": summaryLabel,
"pageControl": pageControl]
let iconVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:|-topMargin-[iconImageView(30)]",
metrics: metrics,
views: views)
allConstraints += iconVerticalConstraints
let topRowHorizontalFormat = """
H:|-leftMargin-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-rightMargin-|
"""
let topRowHorizontalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: topRowHorizontalFormat,
options: [.alignAllCenterY],
metrics: metrics,
views: views)
allConstraints += topRowHorizontalConstraints
let summaryHorizontalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "H:|-horizontalPadding-[summaryLabel]-horizontalPadding-|",
metrics: metrics,
views: views)
allConstraints += summaryHorizontalConstraints
let iconToImageVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[iconImageView]-10-[appImageView]",
metrics: nil,
views: views)
allConstraints += iconToImageVerticalConstraints
let imageToWelcomeVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[appImageView]-10-[welcomeLabel]",
options: [.alignAllCenterX],
metrics: nil,
views: views)
allConstraints += imageToWelcomeVerticalConstraints
let summaryLabelVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[welcomeLabel]-4-[summaryLabel]",
options: [.alignAllLeading, .alignAllTrailing],
metrics: nil,
views: views)
allConstraints += summaryLabelVerticalConstraints
let summaryToPageVerticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:[summaryLabel]-15-[pageControl(9)]-bottomMargin-|",
options: [.alignAllCenterX],
metrics: metrics,
views: views)
allConstraints += summaryToPageVerticalConstraints
NSLayoutConstraint.activate(allConstraints)
Build and run the project, and you'll notice the updated UI looks much better on the iPhone X:
Limitations
VFL makes it possible to write multiple constraints using just one line of code, reducing the burden on your fingertips. However, there are some limitations to the current implementation; a couple of the more notable are important to understand:
- Centering of views
- Using the multiplier component of constraints
Centering of Views
Within Grapevine, you've centered views using the layout options .alignAllCenterY
and .alignAllCenterX
.
Using these means you aligned views with other views respective to horizontal and vertical centers, however this only works if one of the views you're aligning already has enough constraints to describe its horizontal or vertical centers.
While there are tricks you can use to center views using the VFL, there are no guarantees that they'll work in future versions.
Using the Multiplier Component of Constraints
With this, you have the ability to set fixed aspect ratios on views or to do something like make a label take up only 60 percent of its superview's width. Since the VFL creates multiple constraints and returns only an array of un-named constraints, the multiplier cannot be set through the format string.
Note: You could loop through each of the constraints returned by the constraintsWithVisualFormat
method, but you would have to process each of them in turn to determine the NSLayoutAttribute
so that you could correctly set your multiplier. But even then, you still have to replace that constraint because the multiplier isn't mutable.
Note: You could loop through each of the constraints returned by the constraintsWithVisualFormat
method, but you would have to process each of them in turn to determine the NSLayoutAttribute
so that you could correctly set your multiplier. But even then, you still have to replace that constraint because the multiplier isn't mutable.
Now that you know how the Visual Format Language works, you’re ready to take this knowledge and layout your own interfaces.
You've seen how to use layout options to reduce the number of constraints you have to define. You've seen how metrics can be defined not only at compile time, but also at runtime. Lastly, you've seen that there are limitations to the Visual Format Language, but it has more pros than cons and you should take advantage of it where appropriate.
Where To Go From Here?
You can download the finished project here.
Note: Sometimes Xcode has problems when several projects share the same bundle identifier. So, if you've worked through the tutorial and try to run the downloaded final project, you might need to clean the build folder by pressing option and selecting Product \ Clean Build Folder.
Note: Sometimes Xcode has problems when several projects share the same bundle identifier. So, if you've worked through the tutorial and try to run the downloaded final project, you might need to clean the build folder by pressing option and selecting Product \ Clean Build Folder.
For more information on the iPhone X and safe area, be sure to check out the following articles:
- Update your apps for iPhone X
- iPhone X - iOS Human Interface Guidelines
- Positioning Content Relative to the Safe Area
I hope you enjoyed this Visual Format Language tutorial. If you have any questions or comments, please join the forum discussion below!