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.

Leave a rating/review
Save for later
Share
Update note: This tutorial has been updated to iOS 11, Xcode 9 and Swift 4 by József Vesza. The original tutorial was written by Darren Ferguson.

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.

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):

Note: Please use of one of the rectangular iPhones (e.g., iPhone 8) for this part of the tutorial. You’ll see how to handle the iPhone X later.

Initial screen

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:

Auto Layout visual format language

Here’s a step-by-step explanation of the VFL format string:

  • H: indicates horizontal orientation.
  • V: indicates vertical orientation.
  • Not specified: Auto Layout defaults to horizontal orientation.
  • 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)
  • 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)
  1. Direction of your constraints, not required. Can have the following values:
  2. Leading connection to the superview, not required.
  3. View you’re laying out, is required.
  4. Connection to another view, not required.
  5. Trailing connection to the superview, not required.

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
  • 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:

Grapevine-Hidden-Icons

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:

  1. Create a views dictionary that holds string representations of views to resolve inside the format string.
  2. Create a mutable array of constraints. You'll build this up in the rest of the code.
  3. 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.
  4. Set up vertical constraints for the appNameLabel, placing its top edge 23 points from its superview's top edge.
  5. Set up vertical constraints for the skipButton, placing its top edge 20 points from its superview's top edge.
  6. 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 the iconImageView and appNameLabel. Next, a standard spacing of 8 points is placed between the appNameLabel and skipButton. Finally, the skipButton's trailing edge is placed 15 points from the trailing edge of its superview.
  7. Activate the layout constraints using the class method activate(_:) on NSLayoutConstraint. You pass in the allConstraints 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.

Build and run your project. How do the interface elements look now?

Grapevine-Horizontal-Layout

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:

  1. Set up horizontal constraints for the summaryLabel and welcomeLabel, placing them 15 points from the leading and trailing edges of their superview.
  2. Set up vertical constraints for the icon to the app image, with spacing of 10 points
  3. Set up vertical constraints for the app image to the welcome label, with spacing of 10 points
  4. Set up vertical constraints between the welcome label and summary label, with a spacing of 4 points
  5. 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?

Grapevine layout before options

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.

József Vesza

Contributors

József Vesza

Author

Jairo A. Cepeda

Tech Editor

Darren Ferguson

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.