Chapters

Hide chapters

Auto Layout by Tutorials

First Edition · iOS 13 · Swift 5.1 · Xcode 11

Section II: Intermediate Auto Layout

Section 2: 10 chapters
Show chapters Hide chapters

Section III: Advanced Auto Layout

Section 3: 6 chapters
Show chapters Hide chapters

13. Common Auto Layout Issues
Written by Libranner Santos

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Auto Layout is great, but it’s not a magic formula for creating bug-free interfaces. With Auto Layout, you’ll occasionally run into problems. For instance, your layout may too few constraints, too many constraints or conflicting constraints. Although the Auto Layout Engine will try to solve most of these problems for you, it’s crucial to understand how to handle them.

Understanding and solving Auto Layout issues

Under the hood, the Auto Layout Engine reads each of your constraints as a formula for calculating the position and size of your views. When the Auto Layout Engine is unable to satisfy all of the constraints, you’ll receive an error.

There are three types of errors you’ll encounter while working with Auto Layout:

  • Logical Errors: These types of errors usually occur when you inadvertently set up incorrect constraints. Maybe you entered incorrect values for the constants, choose the wrong relationship between the elements or didn’t take into account the different orientations and screen sizes.

  • Unsatisfiable Constraints: You’ll get these errors when it’s impossible for the Auto Layout Engine to calculate a solution. A simple example is when you set one view to have a width < 20 and a width = 100. Since both of those constraints cannot be true at the same time, you’ll get an error.

  • Ambiguous Constraints: When the Auto Layout Engine is trying to satisfy multiple constraints, and there’s more than one possible solution, you’ll get this type of error. When this happens, the engine will randomly select a solution, which may produce unexpected results with your UI.

It’s time to see how these issues look in a real project, starting with Unsatisfiable Constraints.

Unsatisfiable Constraints

Open the starter project for this chapter and build and run.

How to read the logs

Look at the console, and you’ll see some useful information. What you’re seeing is a combination of informative text, constraints in visual format language and even some helpful suggestions:

2019-05-05 04:19:42.439737+0200 DebuggingAutoLayout[4310:346296] [LayoutConstraints] Unable to simultaneously satisfy constraints.
  Probably at least one of the constraints in the following list is one you don’t want. 
  Try this: 
    (1) look at each constraint and try to figure out which you don’t expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600002069270 UIView:0x7fad7dc0f2f0.height == 100   (active)>",
    "<NSLayoutConstraint:0x600002069900 UILayoutGuide:0x600003a355e0'UIViewSafeAreaLayoutGuide'.bottom == UIView:0x7fad7dc0f2f0.bottom + 800   (active)>",
    "<NSLayoutConstraint:0x600002069950 V:|-(0)-[UIView:0x7fad7dc0f2f0]   (active, names: '|':UIView:0x7fad7dc0d510 )>",
    "<NSLayoutConstraint:0x600002074910 'UIView-Encapsulated-Layout-Height' UIView:0x7fad7dc0d510.height == 667   (active)>",
    "<NSLayoutConstraint:0x600002069860 'UIViewSafeAreaLayoutGuide-bottom' V:[UILayoutGuide:0x600003a355e0'UIViewSafeAreaLayoutGuide']-(0)-|   (active, names: '|':UIView:0x7fad7dc0d510 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002069270 UIView:0x7fad7dc0f2f0.height == 100   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

Symbolic Breakpoints

When you create a Symbolic Breakpoint at UIViewAlertForUnsatisfiableConstraints, the app will stop running if it finds an unsatisfiable constraint. This is useful because sometimes the app may appear OK, when in fact, it has some issues you need to address.

Using Interface Builder to solve conflicts

Open Main.storyboard. In the document outline, click the red circle with an arrow at the right of the Game Scene, and you’ll see a list of the conflicting constraints.

private func createBoard() {
  var tagFirstColumn = 0
  let numberOfColumns = 4
  let numberOfRows = 4
    
  //1
  let boardStackView = UIStackView()
  boardStackView.axis = .vertical
  boardStackView.distribution = .fillEqually
  boardStackView.spacing = 10
  boardStackView.translatesAutoresizingMaskIntoConstraints =
    false

  //2
  containerView.addSubview(boardStackView)
  view.addSubview(containerView)
    
  //3
  NSLayoutConstraint.activate([
    containerView.topAnchor.constraint(equalTo: 
      boardStackView.topAnchor),
    containerView.leadingAnchor.constraint(equalTo: 
      boardStackView.leadingAnchor),
    containerView.trailingAnchor.constraint(equalTo: 
      boardStackView.trailingAnchor),
    containerView.bottomAnchor.constraint(equalTo: 
      boardStackView.bottomAnchor)
  ])
    
  for _ in 0..<numberOfRows {
    //4
    let boardRowStackView = UIStackView()
    boardRowStackView.axis = .horizontal
    boardRowStackView.distribution = .equalSpacing
    boardRowStackView.spacing = 10

    //5
    for otherIndex in 0..<numberOfColumns {
      let button =
        createButtonWithTag(otherIndex + tagFirstColumn)
      buttons.append(button)
      boardRowStackView.addArrangedSubview(button)
    }
  
    //6
    boardStackView.addArrangedSubview(boardRowStackView)
  
    //7
    tagFirstColumn += numberOfColumns
  }
    
  //8
  blockBoard()
}

Ambiguous layouts

When the Auto Layout Engine arrives at multiple solutions for a system of constraints, you’ll have what’s known as an ambiguous layout.

UIView debug methods

UIView comes with four useful methods that you can use for debugging your Auto Layout issues:

Dealing with ambiguous layouts

Go back to the starter project and build and run.

po [[UIWindow keyWindow] _autolayoutTrace]

*UIStackView:0x7f8be3405bf0
|   |   |   |   *<_UIOLAGapGuide: 0x6000018f9e00 - "UISV-distributing", layoutFrame = {{0, 50}, {0, 10}}, owningView = <UIStackView: 0x7f8be3405bf0; frame = (0 0; 230 230); layer = <CATransformLayer: 0x600002497940>>>- AMBIGUOUS LAYOUT for _UIOLAGapGuide:0x6000018f9e00'UISV-distributing'.minX{id: 595}, _UIOLAGapGuide:0x6000018f9e00'UISV-distributing'.Width{id: 596}

po [[UIWindow keyWindow] _autolayoutTrace]

Visual debugging

To solve the remaining issue, you’ll use the Debug View Hierarchy, which gives a visual way to look at how the layout is structured.

View hierarchy toolbar

So what does this new toolbar do? From left to right:

Handling runtime issues

Using the memory address copied from the layout trace, paste it on the filter located at the bottom-left side of the screen.

labelConstraints.append(
  timerLabel.centerXAnchor.constraint(
    equalTo: safeAreaLayoutGuide.centerXAnchor))

Common performance issues

The Auto Layout Engines does a lot of things for you. It acts as a dependency cache manager, making things faster, and Apple makes performance-related improvements nearly every year. Nonetheless, you have to be aware of how it works to avoid the unnecessary use of resources.

Churning

Churning is a common issue and one that can easily fly above any developer’s head. It usually happens when you create multiple constraints — many times without them being necessary.

override func updateConstraints() {
  NSLayoutConstraint.deactivate(labelConstraints)
  labelConstraints.removeAll()
    
  labelConstraints.append(
    timerLabel.centerXAnchor.constraint(
      equalTo: safeAreaLayoutGuide.centerXAnchor))
  labelConstraints.append(
    timerLabel.centerYAnchor.constraint(
      equalTo: safeAreaLayoutGuide.centerYAnchor))
    
  NSLayoutConstraint.activate(labelConstraints)
  super.updateConstraints()
}
override func updateConstraints() {
  if labelConstraints.isEmpty {
    labelConstraints.append(
      timerLabel.centerXAnchor.constraint(
        equalTo: safeAreaLayoutGuide.centerXAnchor))
    labelConstraints.append(
      timerLabel.centerYAnchor.constraint(
        equalTo: safeAreaLayoutGuide.centerYAnchor))
      
    NSLayoutConstraint.activate(labelConstraints)
  }
  super.updateConstraints()
}

Layout feedback loop

Until now, you haven’t seen the second tab of the app: History.

-UIViewLayoutFeedbackLoopDebuggingThreshold 100

DebuggingAutoLayout[10441:75492] [LayoutLoop] >>>UPSTREAM LAYOUT DIRTYING<<< About to send -setNeedsLayout to layer for <UITableView: 0x7fd8ec82ae00; f={{0, 0}, {375, 667}} > under -viewDidLayoutSubviews for <UITableView: 0x7fd8ec82ae00; f={{0, 0}, {375, 667}}
  override func viewDidLayoutSubviews() {
    view.setNeedsLayout()
  }

Key points

  • There are three main types of Auto Layout issues: ambiguous layouts, unsatisfiable constraints and logic errors.
  • You can solve Auto Layout issues faster using UIView methods, Debug View Hierarchy and Interface Builder.
  • To avoid performance issues, you need to understand how the Render Loop and Auto Layout Engine works.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now