Auto Layout & UIKit Layout
Irbisa · cheatsheetSeptember 13, 2026

Auto Layout & UIKit Layout

Constraints, intrinsic content size, UIStackView, frames, safe area

Junior Developer20 itemscompressed for a skim
  1. 01

    What is Auto Layout? How is it different from frame-based layout?

    Easy

    Auto Layout is a constraint-based layout engine.

    let a = UIView(), b = UIView()
    [a, b].forEach {
      $0.translatesAutoresizingMaskIntoConstraints = false
      view.addSubview($0)
    }
    …
  2. 02

    What is intrinsic content size? How does it relate to compression resistance and hugging?

    Medium

    A view's intrinsic content size is the size it wants to be based on its own content — a label sized to its text, a button to its title plus insets.

    let title = UILabel(); title.text = "Long article title that wraps"
    let badge = UILabel(); badge.text = "NEW"
    
    // Title should yield space; badge should hold its size
    title.setContentHuggingPriority(.defaultLow, for: .horizontal)
    title.setContentCompressionResistancePriority(.defaultLow - 1, for: .horizontal)
    …
  3. 03

    Why reach for UIStackView instead of writing constraints between siblings?

    Easy

    UIStackView generates the constraints between its arranged subviews for you: you set an axis, a distribution and an alignment, then constrain only the stack itself.

    // Vertical stack of content
    let content = UIStackView(arrangedSubviews: [
      titleLabel,
      subtitleLabel,
      metadataRow,
    ])
    …
  4. 04

    What is the safe area? How do you handle it in UIKit?

    Medium

    Safe area is the region of the screen NOT covered by system UI: status bar, navigation bar, tab bar, home indicator, and notches/cutouts on modern iPhones.

    // Pin a button to the bottom safe area (above the home indicator)
    view.addSubview(button)
    button.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
      button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
    …
  5. 05

    How do you debug Auto Layout issues like 'unable to satisfy constraints'?

    Medium

    When Auto Layout can't find a valid solution it prints a long diagnostic and breaks one constraint.

    // Name constraints for readable logs
    let c = button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8)
    c.identifier = "buttonBottom"
    c.isActive = true
    
    // Make an optional constraint break gracefully
    …
  6. 06

    What is the difference between layoutIfNeeded, setNeedsLayout, and layoutSubviews?

    Medium

    setNeedsLayout schedules a layout pass, layoutIfNeeded runs one immediately, and layoutSubviews is the callback UIKit invokes during that pass — you call the first two and override the third.

    // Animate by changing constraint constants
    leadingConstraint.constant = 100
    view.setNeedsLayout()
    UIView.animate(withDuration: 0.3) {
      view.layoutIfNeeded()           // forces the new layout to apply inside the animation
    }
    …
  7. 07

    You added constraints in code but every view is stacked in the top-left corner — what did you forget?

    Easy

    A view created in code still has translatesAutoresizingMaskIntoConstraints set to true, which turns its frame and autoresizing mask into real constraints that fight the ones you wrote.

    let card = UIView()
    card.translatesAutoresizingMaskIntoConstraints = false   // the line everyone forgets once
    view.addSubview(card)
    NSLayoutConstraint.activate([
      card.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
      card.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
    …
  8. 08

    What has to be true for a table view cell to size itself to its content?

    Medium

    A self-sizing cell needs an unbroken chain of vertical constraints from the top of contentView to its bottom, so Auto Layout can solve for the height instead of falling back on a fixed one.

    final class MessageCell: UITableViewCell {
      private let title = UILabel()
      private let body = UILabel()
    
      override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    …
  9. 09

    How do you lay out content inside a UIScrollView with Auto Layout so it actually scrolls?

    Medium

    A scroll view computes its content size from its subviews' constraints, so the content has to be pinned to contentLayoutGuide on all four edges and take its width from frameLayoutGuide.

    let scrollView = UIScrollView()
    let content = UIStackView()          // one container, pinned to the content guide
    content.axis = .vertical
    content.spacing = 16
    [scrollView, content].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
    view.addSubview(scrollView)
    …
  10. 10

    A layout has to change when some state flips — do you change the constant, swap which constraints are active, or change priority?

    Medium

    Change the constant for anything you would animate, swap active constraints for a structural change, and change priority only to decide which of two competing constraints wins.

    private var heightConstraint: NSLayoutConstraint!   // keep the reference, change the constant
    
    func setExpanded(_ expanded: Bool, animated: Bool) {
      heightConstraint.constant = expanded ? 240 : 88
      guard animated else { view.layoutIfNeeded(); return }
      UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }
    …
  11. 11

    What is the difference between pinning to a view's edge, its layoutMarginsGuide, and its readableContentGuide?

    Easy

    Pinning to the edge gives you the literal bounds, layoutMarginsGuide adds the view's own margins, and readableContentGuide caps the width so a paragraph does not run edge to edge on a large screen.

    // Three different edges to pin to, with three different meanings
    label.leadingAnchor.constraint(equalTo: view.leadingAnchor)                      // literal bounds
    label.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor)   // plus margins
    label.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor) // readable width
    
    // Margins are directional, so they mirror in right-to-left languages
    …
  12. 12

    What breaks a UIKit layout in Arabic or at the largest accessibility text size, and how do you prevent it?

    Medium

    Use leading and trailing anchors instead of left and right, and let text drive height instead of hard-coding it — those two habits prevent most of the damage.

    // Leading and trailing mirror automatically; left and right never do
    NSLayoutConstraint.activate([
      icon.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
      label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 8),
      label.trailingAnchor.constraint(lessThanOrEqualTo: view.layoutMarginsGuide.trailingAnchor),
    ])
    …
  13. 13

    Every button in a horizontal UIStackView came out the same width and the icon got stretched to the full row height — which two properties are wrong?

    Easy

    distribution divides space along the axis and alignment positions views across it: the equal widths come from .fillEqually, the stretched icon from the default alignment .fill.

    let row = UIStackView(arrangedSubviews: [icon, title, chevron])
    row.axis = .horizontal
    row.spacing = 8
    
    // WRONG: every arranged view ends up the same width, and the icon is stretched
    // to the row's height because .fill alignment resizes across the axis too.
    …
  14. 14

    The app crashes with "Unable to activate constraint ... because they have no common ancestor" — what did the setup code do in the wrong order?

    Easy

    It activated a constraint before both views were in the same hierarchy: activating installs the constraint on the nearest common ancestor of the two items, and a view with no superview has no ancestor to install it on.

    // WRONG: the anchors are activated while badge is still floating with no superview
    let badge = UIView()
    NSLayoutConstraint.activate([
      badge.topAnchor.constraint(equalTo: card.topAnchor),      // crash: no common ancestor
      badge.trailingAnchor.constraint(equalTo: card.trailingAnchor),
    ])
    …
  15. 15

    Your custom badge view draws a longer number after an update but keeps its old width — what is missing from the view's implementation?

    Easy

    The view never told Auto Layout that its natural size changed: override intrinsicContentSize, and call invalidateIntrinsicContentSize() every time the content that feeds it changes.

    final class BadgeView: UIView {
      private let inset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
    
      var count: Int = 0 {
        didSet {
          guard count != oldValue else { return }
    …
  16. 16

    A table view's tableHeaderView renders zero points tall no matter what its internal constraints say — how do you size a view to its content by hand?

    Medium

    tableHeaderView is one of the few views UIKit never self-sizes, so you measure it yourself with systemLayoutSizeFitting and assign the resulting frame.

    func sizeTableHeader() {
      guard let header = tableView.tableHeaderView else { return }
    
      // Width first: a multi-line label measured at zero width reports one line
      header.frame.size.width = tableView.bounds.width
      header.setNeedsLayout()
    …
  17. 17

    A text field at the bottom of a form sits under the keyboard — what is the current UIKit way to lift it, and where did the old notification code go wrong?

    Medium

    Constrain the field to view.keyboardLayoutGuide.topAnchor: since iOS 15 that guide tracks the keyboard's frame in your view's coordinates and animates with it, so one constraint replaces the whole notification pipeline.

    // iOS 15+: one constraint, animated in step with the keyboard
    composer.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      composer.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -8),
      composer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      composer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    …
  18. 18

    One screen pins the CPU at 100% and freezes while layoutSubviews runs over and over — what causes an Auto Layout feedback loop?

    Medium

    Something inside the layout pass invalidates the layout again: you changed constraints from geometry that the same pass just produced, so every pass schedules another one.

    // WRONG: the constant is recomputed from the frame this pass produced,
    // which dirties the layout and schedules the next pass. Forever.
    override func layoutSubviews() {
      super.layoutSubviews()
      insetConstraint.constant = bounds.width * 0.1
      layoutIfNeeded()                       // and this makes it recurse immediately
    …
  19. 19

    The design is two columns on iPad and one on iPhone — do you check the device idiom, the screen width, or something else?

    Medium

    Check the trait collection's horizontalSizeClass, because it describes the space your view actually has, while the idiom and the screen describe hardware that may have nothing to do with it.

    // WRONG: hardware questions, answered before the window even has a size
    if UIDevice.current.userInterfaceIdiom == .pad { showTwoColumns() }
    let width = UIScreen.main.bounds.width      // deprecated in iOS 16; not your window
    
    // RIGHT: the trait describes the space this view actually got
    private func applyLayout() {
    …
  20. 20

    Two icons pinned to the same leading edge look misaligned because one asset carries a glow around the artwork — what is Auto Layout actually aligning?

    Medium

    Auto Layout aligns alignment rects, not frames — by default the two are identical, but a view can declare that part of its frame is decoration that should not count.

    // WRONG: a magic constant that cancels the asset's glow at one call site
    NSLayoutConstraint.activate([
      glowIcon.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: -4),
      plainIcon.leadingAnchor.constraint(equalTo: container.leadingAnchor),
    ])
    …