Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Auto Layout Visual Format Language Tutorial

$
0
0
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.
VisualFormatIcon

Learn how to layout elements using ASCII art!

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:

  1. 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.
  2. 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)
  3. View you’re laying out, is required.
  4. Connection to another view, not required.
  5. 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:

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.

Layout Options

Layout options provide the ability to manipulate view constraints perpendicular to the current layout orientation being defined.

Applying vertical centering to all views in a horizontal layout orientation by using NSLayoutFormatOptions.AlignAllCenterY is an example of layout options.

You wouldn't use this option in vertical orientation since there's no sense in trying to set all of the views' centers vertically while laying them out vertically, edge by edge. It's also not provided for vertical orientation, so there you go.

Next, you'll see how layout options are useful when it comes to constructing layouts. Remove the following code from viewDidLoad():

let nameLabelVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
  "V:|-23-[appNameLabel]",
  metrics: nil,
  views: views)
allConstraints += nameLabelVerticalConstraints

let skipButtonVerticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
  "V:|-20-[skipButton]",
  metrics: nil,
  views: views)
allConstraints += skipButtonVerticalConstraints

You're just removing the vertical constraints from the appNameLabel and skipButton. Instead, you're going to use the layout options to give them a vertical position.

Find the code that creates topRowHorizontalConstraints and set the options parameter to [.alignAllCenterY]. It should now look like the following:

let topRowHorizontalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "H:|-15-[iconImageView(30)]-[appNameLabel]-[skipButton]-15-|",
  options: [.alignAllCenterY],
  metrics: nil,
  views: views)
allConstraints += topRowHorizontalConstraints

You've provided the NSLayoutFormatOption .alignAllCenterY that takes each view inside the format string and creates layout constraints to align each of them by their vertical centers. This code works since the iconImageView has previously defined vertical layout constraints, including its height. Thus, the appNameLabel and skipButton are vertically centered with the iconImageView.

If you build and run now, the layout will look exactly the same, but you know the code is better :]

Remove the code that creates welcomeHorizontalConstraints and adds it to the constraints array.

This removes the horizontal constraints from the welcomeLabel.

Next, update the options when creating summaryLabelVerticalConstraints definition to the following:

let summaryLabelVerticalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "V:[welcomeLabel]-4-[summaryLabel]",
  options: [.alignAllLeading, .alignAllTrailing],
  metrics: nil,
  views: views)
allConstraints += summaryLabelVerticalConstraints

This code adds the NSLayoutFormatOptions options .alignAllLeading and .alignAllTrailing to the options array. The welcomeLabel and summaryLabel's leading and trailing spacing will be aligned 15 points from the leading and trailing edge of their superview. This occurs because the summaryLabel already has its horizontal constraints defined.

Again, this will give you the same layout as you already had, but in a better way.

Next, update the options when you create summaryToPageVerticalConstraints to match the following:

let summaryToPageVerticalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "V:[summaryLabel]-15-[pageControl(9)]-15-|",
  options: [.alignAllCenterX],
  metrics: nil,
  views: views)
allConstraints += summaryToPageVerticalConstraints

Adding this option aligns the views on the center X axis. Do the same for imageToWelcomeVerticalConstraints:

let imageToWelcomeVerticalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "V:[appImageView]-10-[welcomeLabel]",
  options: [.alignAllCenterX],
  metrics: nil,
  views: views)
allConstraints += imageToWelcomeVerticalConstraints

Build and run your project; how do the interface elements look?

Grapevine-SublayoutViewHeights

Feeling centered yet? Layout options have taken you closer to that nice user interface you're after.

NSLayoutFormat Options Quick Reference

Here are the options you've used in Grapevine:

  • .alignAllCenterX -- align interface elements using NSLayoutAttribute.centerX.
  • .alignAllCenterY -- align interface elements using NSLayoutAttribute.centerY.
  • .alignAllLeading -- align interface elements using NSLayoutAttribute.leading.
  • .alignAllTrailing -- align interface elements using NSLayoutAttribute.trailing.

Below are some more of these options:

  • .alignAllLeft -- align interface elements using NSLayoutAttribute.left.
  • .alignAllRight -- align interface elements using NSLayoutAttribute.right.
  • .alignAllTop -- align interface elements using NSLayoutAttribute.top.
  • .alignAllBottom -- align interface elements using NSLayoutAttribute.bottom.
  • .alignAllLastBaseline -- align interface elements using NSLayoutAttribute.lastBaseline.

You can find the complete list in the documentation.

Note: At least one of the elements must have enough defined perpendicular constraints for layout options to work. See the example below:

  NSLayoutConstraints.constraintsWithVisualFormat(
    "V:[topView]-[middleView]-[bottomView]",
    options: [.alignAllLeading],
    metrics: nil,
    views: ["topView": topView, "middleView": middleView, "bottomView": bottomView"])

The topView, middleView or bottomView must have constraints defining the position of their leading edge for Auto Layout to generate the correct constraints.

And now for a new concept! Meet Metrics.

Metrics

Metrics are a dictionary of number values that can appear inside the VFL format string. These are particularly useful if you have standardized spacing or calculated size values that you can't type directly into the format string.

Add the following constant declaration above your @IBOutlet declarations in ViewController.swift:

private enum Metrics {
  static let padding: CGFloat = 15.0
  static let iconImageViewWidth: CGFloat = 30.0
}

Now you have a constant for the padding, and icon image width, you can create a metrics dictionary and utilize the constant. Add the following code above your views declaration in viewDidLoad():

let metrics = [
  "horizontalPadding": Metrics.padding,
  "iconImageViewWidth": Metrics.iconImageViewWidth]

The above code creates a dictionary of key / value pairs to be substituted into the format string.

Next, replace the topRowHorizontalConstraints and summaryHorizontalConstraints definitions with the following:

let topRowHorizontalFormat = """
  H:|-horizontalPadding-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-horizontalPadding-|
  """
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

You're replacing the hard coded values in the format string with placeholders that represent keys from the metrics dictionary. You also set the metrics parameter to the metrics dictionary.

Auto Layout will perform string substitution, replacing the placeholder text with the value inside the metrics dictionary. In the above case, horizontalPadding will be replaced with the constant 15 points, and iconImageViewWidth will be replaced with the constant 30 points.

You've removed a repeatedly used magic number and replaced it with a nice clean variable. If you decide to change the padding, you only have to change one thing. Isn't that better? The metrics dictionary isn't limited to constants either; if you need to calculate things at run time and put them in the dictionary, that's fine too.

The final piece of the puzzle to place is how you lay out interface elements when your view controllers are embedded inside a UINavigationController or UITabBarController.

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.

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.

For more information on the iPhone X and safe area, be sure to check out the following articles:

I hope you enjoyed this Visual Format Language tutorial. If you have any questions or comments, please join the forum discussion below!

The post Auto Layout Visual Format Language Tutorial appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>