iosNudge

How does Auto Layout work? Explain hugging and compression resistance.

MediumAsked oftenUIKit 2 min read

TL;DR

Auto Layout turns your constraints into linear equations and solves them to get each view's frame. Each constraint has a priority from 1 to 1000. 1000 means required. Content hugging says "do not grow bigger than my content". Compression resistance says "do not shrink smaller than my content".

A constraint is an equation

item1.attribute = multiplier × item2.attribute + constant

let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)

NSLayoutConstraint.activate([
    label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
    label.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -16),
    label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20)
])

You need enough constraints for x, y, width and height. Labels, buttons and image views have an intrinsic content size, so they can skip width and height.

Why translatesAutoresizingMaskIntoConstraints = false

By default UIKit creates constraints from the old autoresizing mask. These fight with your constraints. Turn it off for views you lay out in code. Interface Builder does this for you.

Hugging and compression resistance

Two labels in a row. Only one can stretch. Which one?

// Title should stay small. Value should take the extra space.
titleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)   // 750
valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)    // 250

// When space is tight, cut the value, never the title.
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
PriorityQuestion it answersDefault
Content huggingWho grows when there is extra space? Lower value grows first.250 (labels in IB: 251)
Compression resistanceWho shrinks when space is short? Lower value shrinks first.750

Priorities on normal constraints

A constraint below 1000 is optional. The engine tries to satisfy it but can break it.

let preferredWidth = card.widthAnchor.constraint(equalToConstant: 400)
preferredWidth.priority = .defaultHigh // 750, can break on small screens
let maxWidth = card.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, constant: -32)
NSLayoutConstraint.activate([preferredWidth, maxWidth])

Ambiguous vs unsatisfiable

  • Ambiguous: not enough constraints. More than one answer. Views jump or have zero size.
  • Unsatisfiable: required constraints conflict. UIKit logs "Unable to simultaneously satisfy constraints" and breaks one.

Debug with the View Debugger in Xcode, or view.hasAmbiguousLayout and exerciseAmbiguityInLayout(). Give constraints an identifier so the console log is readable.

Animating constraint changes

heightConstraint.constant = 300
UIView.animate(withDuration: 0.3) {
    self.view.layoutIfNeeded()
}

Change the constant first. Then call layoutIfNeeded() on the superview inside the animation block.

UIStackView

UIStackView creates the constraints for you. Use it for rows and columns. It reduces bugs a lot.

Common follow-up questions

  • Is Auto Layout slow? It is fast for normal screens. Very deep hierarchies and constant constraint churn in cells can hurt scrolling.
  • Activate one by one or in a batch? Use NSLayoutConstraint.activate([...]). It is faster and clearer.
  • What is a layout guide? An invisible rectangle you can constrain to, like safeAreaLayoutGuide or layoutMarginsGuide.
  • How do self-sizing cells work? Constrain content top to bottom inside contentView. The cell height comes from the constraints.
  • Does SwiftUI use Auto Layout? No. SwiftUI has its own layout system: parent proposes a size, child picks its size.