Irbisa · Cheatsheet

Auto Layout & UIKit Layout

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

Junior Developer · 25 questions

  1. 01

    What is Auto Layout, and how is it different from frame-based layout?

    Auto Layout is a constraint-based layout system: you describe relationships between views ("16 pt below the title, as wide as the card"), and a solver turns them into frames on every layout pass.

  2. 02

    What is the Auto Layout constraint equation, and what are layout anchors?

    Every constraint is a linear equation item1.attribute = multiplier × item2.attribute + constant, where the relation can also be ≤ or ≥.

    // The equation spelled out with the original initializerNSLayoutConstraint(item: avatar, attribute: .leading,                   relatedBy: .equal,                   toItem: card, attribute: .leading,                   multiplier: 1, constant: 16).isActive = true…
  3. 03

    How many constraints does a view need before its layout is unambiguous?

    Enough to fix four values (x position and width, y position and height), which usually means four constraints, two per axis.

  4. 04

    You constrained a view created in code, but it never appears where expected and the console lists broken constraints. What is wrong here?

    override func viewDidLoad() {  super.viewDidLoad()  let card = UIView()  card.backgroundColor = .systemBlue  view.addSubview(card)  NSLayoutConstraint.activate([    card.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),    card.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),    card.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),    card.heightAnchor.constraint(equalToConstant: 120),  ])}

    card still has translatesAutoresizingMaskIntoConstraints set to true, so UIKit turns its frame (.zero) and autoresizing mask into constraints that contradict yours.

  5. 05

    This setup code crashes with "Unable to activate constraint ... because they have no common ancestor". What did it do in the wrong order?

    let badge = UILabel()badge.text = "NEW"badge.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([  badge.topAnchor.constraint(equalTo: card.topAnchor, constant: 8),  badge.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -8),]) card.addSubview(badge)

    It activates the constraints before badge is in the view hierarchy.

  6. 06

    What is the safe area, and how do you keep content inside it in UIKit?

    The safe area is the part of a view not covered by the status bar, navigation and tab bars, the home indicator or the Dynamic Island.

    // Full-bleed background: the view's edges, not the safe areaNSLayoutConstraint.activate([  background.topAnchor.constraint(equalTo: view.topAnchor),  background.bottomAnchor.constraint(equalTo: view.bottomAnchor),  background.leadingAnchor.constraint(equalTo: view.leadingAnchor),  background.trailingAnchor.constraint(equalTo: view.trailingAnchor),…
  7. 07

    Why do you constrain to leadingAnchor and trailingAnchor rather than leftAnchor and rightAnchor?

    Leading and trailing follow the reading direction, so the layout mirrors automatically in Arabic and Hebrew; left and right are physical sides and never flip.

  8. 08

    What is UIStackView, and what are its advantages and drawbacks?

    UIStackView arranges its subviews in a row or a column and creates the constraints between them for you: you set axis, distribution, alignment and spacing, and constrain only the stack itself.

    let metadataRow = UIStackView(arrangedSubviews: [authorLabel, dateLabel])metadataRow.axis = .horizontalmetadataRow.spacing = 4 let content = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel, metadataRow])content.axis = .vertical…
  9. 09

    In a horizontal UIStackView every item came out the same width and the icon is stretched to the row's height. Which two properties are wrong?

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

    let row = UIStackView(arrangedSubviews: [icon, title, chevron])row.axis = .horizontalrow.spacing = 8 // wrong: equal widths, and the icon stretched to the row height// row.distribution = .fillEqually…
  10. 10

    What is intrinsic content size, and how do content hugging and compression resistance decide which view gives way?

    Intrinsic content size is the size a view wants from its own content: a label's text, a button's title plus insets.

    let title = UILabel()title.text = "A long article title that will not fit"let badge = UILabel()badge.text = "NEW" // The title stretches into free space and truncates first…
  11. 11

    What do @IBDesignable and @IBInspectable do, and what do teams use instead of live rendering today?

    @IBInspectable exposes a custom view's property in Interface Builder's Attributes inspector, and @IBDesignable makes Interface Builder build the view and render it on the canvas.

    @IBDesignablefinal class RoundedButton: UIButton {  @IBInspectable var cornerRadius: CGFloat = 0 {    didSet { layer.cornerRadius = cornerRadius }  }…
  12. 12

    What are constraint priorities, and why do people use 999 instead of 1000 for a constraint that must hold?

    A priority from 1 to 1000 says how hard the solver tries to satisfy a constraint: 1000 (.required) must hold, anything lower is optional: the solver satisfies it as closely as it can and gives way without a log message when it conflicts with something stronger.

    // Preferred width: 320 if it fits, but never past the marginslet preferred = card.widthAnchor.constraint(equalToConstant: 320)preferred.priority = .defaultHigh   // 750: yields to required constraintsNSLayoutConstraint.activate([  preferred,  card.centerXAnchor.constraint(equalTo: view.centerXAnchor),…
  13. 13

    This badge view draws the new number after count changes but keeps its old width. What is missing?

    final class BadgeView: UIView {  var count = 0 {    didSet { setNeedsDisplay() }  }   override var intrinsicContentSize: CGSize {    let text = "\(count)" as NSString    let font = UIFont.preferredFont(forTextStyle: .caption1)    let size = text.size(withAttributes: [.font: font])    return CGSize(width: ceil(size.width) + 16, height: ceil(size.height) + 8)  }   override func draw(_ rect: CGRect) {    // draws the pill and the number  }}

    didSet never calls invalidateIntrinsicContentSize().

  14. 14

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

    The edge is the view's literal bounds, layoutMarginsGuide is the bounds minus the view's margins, and readableContentGuide additionally caps the width so lines of text stay a comfortable length on wide screens.

    // Three edges with three meaningslabel.leadingAnchor.constraint(equalTo: view.leadingAnchor)                      // literal boundslabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor)   // plus marginslabel.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor) // readable width // Directional margins mirror in right-to-left languages…
  15. 15

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

    setNeedsLayout() marks a view as needing layout in the next update cycle, layoutIfNeeded() performs any pending layout right now, and layoutSubviews() is the method UIKit calls during that layout; you call the first two and override the third.

    final class GradientView: UIView {  private let gradient = CAGradientLayer()   override init(frame: CGRect) {    super.init(frame: frame)    layer.addSublayer(gradient)…
  16. 16

    A layout must change when some state flips. Do you change the constant, swap active constraints, or change a priority?

    Change the constant when only a number changes (it animates cleanly), swap active constraint sets when the relationships themselves change, and change priorities when two competing constraints should take turns winning.

    private var heightConstraint: NSLayoutConstraint!   // stored once, reused func setExpanded(_ expanded: Bool, animated: Bool) {  heightConstraint.constant = expanded ? 240 : 88  guard animated else { view.setNeedsLayout(); return }  UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }…
  17. 17

    Find the bug: the panel should slide up smoothly, but it jumps to its new position with no animation.

    final class PlayerViewController: UIViewController {  private let panel = UIView()  // activated in viewDidLoad with leading, trailing and height 300  private lazy var panelBottom = panel.bottomAnchor.constraint(    equalTo: view.bottomAnchor, constant: 300)  // starts off-screen   func showPanel() {    panelBottom.constant = 0    UIView.animate(withDuration: 0.3) {      self.panel.layoutIfNeeded()    }  }}

    layoutIfNeeded() is called on panel, but the changed constraint positions panel inside view, so the block moves nothing.

  18. 18

    The console prints "Unable to simultaneously satisfy constraints". How do you find the culprit?

    Read the log: it lists the set of constraints that cannot all hold and names the one it broke.

  19. 19

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

    There must be an unbroken chain of vertical constraints from the top of contentView to its bottom, so Auto Layout can compute the height; the table then uses UITableView.automaticDimension, which is already the default.

    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)…
  20. 20

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

    A scroll view computes its content size from its content's constraints, so pin the content to all four edges of contentLayoutGuide and take its width from frameLayoutGuide.

    let scrollView = UIScrollView()let content = UIStackView()            // one container for everythingcontent.axis = .verticalcontent.spacing = 16[scrollView, content].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }view.addSubview(scrollView)…
  21. 21

    A text field at the bottom of a form ends up under the keyboard. What is the current UIKit way to lift it, and what did the old notification code get wrong?

    Constrain the field's bottom to view.keyboardLayoutGuide.topAnchor.

    // iOS 15+: one constraint, animated together with the keyboardNSLayoutConstraint.activate([  composer.leadingAnchor.constraint(equalTo: view.leadingAnchor),  composer.trailingAnchor.constraint(equalTo: view.trailingAnchor),  composer.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor, constant: -8),  scrollView.bottomAnchor.constraint(equalTo: composer.topAnchor),   // content stays visible…
  22. 22

    What are size classes, and how do you switch between a two-column iPad layout and a one-column iPhone layout?

    Size classes are coarse traits (.compact or .regular) for the width and height your view actually gets.

    // wrong: a hardware question; an iPad in Split View is still .pad but gets a compact width// if UIDevice.current.userInterfaceIdiom == .pad { showTwoColumns() } // right: the trait describes the space this view actually gotprivate func applyLayout() {  let regular = traitCollection.horizontalSizeClass == .regular…
  23. 23

    How do you handle device rotation in a UIKit layout?

    Mostly you do not: constraints relative to the parent and the safe area re-solve for the new size on their own.

    override func viewWillTransition(to size: CGSize,                                 with coordinator: UIViewControllerTransitionCoordinator) {  super.viewWillTransition(to: size, with: coordinator)  let wide = size.width > size.height  coordinator.animate(alongsideTransition: { _ in    self.contentStack.axis = wide ? .horizontal : .vertical…
  24. 24

    One screen pins the CPU at 100% and freezes while layoutSubviews() runs over and over. What causes an Auto Layout feedback loop?

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

    // wrong: the constant is derived from the frame this pass produced; whenever it// changes this view's own size, the next pass gets new input and the cycle never// ends. layoutIfNeeded() starts that next pass inside this one.override func layoutSubviews() {  super.layoutSubviews()  insetConstraint.constant = bounds.width * 0.1…
  25. 25

    A scrolling list drops frames and Instruments shows the time in Auto Layout. What do you check and what do you change?

    Find out how much layout work each cell does per scroll step, then cut it: fewer views and constraints per cell, no constraint changes on reuse, and frames for the few hot cells that still cost too much.