Irbisa · Cheatsheet
Auto Layout & UIKit Layout
Constraints, intrinsic content size, UIStackView, frames, safe area
What is Auto Layout, and how is it different from frame-based layout?
What is the Auto Layout constraint equation, and what are layout anchors?
// The equation spelled out with the original initializerNSLayoutConstraint(item: avatar, attribute: .leading, relatedBy: .equal, toItem: card, attribute: .leading, multiplier: 1, constant: 16).isActive = true…How many constraints does a view need before its layout is unambiguous?
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), ])}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)What is the safe area, and how do you keep content inside it in UIKit?
// 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),…Why do you constrain to
leadingAnchorandtrailingAnchorrather thanleftAnchorandrightAnchor?What is
UIStackView, and what are its advantages and drawbacks?let metadataRow = UIStackView(arrangedSubviews: [authorLabel, dateLabel])metadataRow.axis = .horizontalmetadataRow.spacing = 4 let content = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel, metadataRow])content.axis = .vertical…In a horizontal
UIStackViewevery item came out the same width and the icon is stretched to the row's height. Which two properties are wrong?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…What is intrinsic content size, and how do content hugging and compression resistance decide which view gives way?
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…What do
@IBDesignableand@IBInspectabledo, and what do teams use instead of live rendering today?@IBDesignablefinal class RoundedButton: UIButton { @IBInspectable var cornerRadius: CGFloat = 0 { didSet { layer.cornerRadius = cornerRadius } }…What are constraint priorities, and why do people use 999 instead of 1000 for a constraint that must hold?
// 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),…This badge view draws the new number after
countchanges 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 }}What is the difference between pinning to a view's edge, its
layoutMarginsGuideand itsreadableContentGuide?// 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…What is the difference between
setNeedsLayout(),layoutIfNeeded()andlayoutSubviews()?final class GradientView: UIView { private let gradient = CAGradientLayer() override init(frame: CGRect) { super.init(frame: frame) layer.addSublayer(gradient)…A layout must change when some state flips. Do you change the
constant, swap active constraints, or change a priority?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() }…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() } }}The console prints "Unable to simultaneously satisfy constraints". How do you find the culprit?
What has to be true for a table view cell to size itself to its content?
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)…How do you lay out content inside a
UIScrollViewwith Auto Layout so that it actually scrolls?let scrollView = UIScrollView()let content = UIStackView() // one container for everythingcontent.axis = .verticalcontent.spacing = 16[scrollView, content].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }view.addSubview(scrollView)…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?
// 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…What are size classes, and how do you switch between a two-column iPad layout and a one-column iPhone layout?
// 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…How do you handle device rotation in a UIKit layout?
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…One screen pins the CPU at 100% and freezes while
layoutSubviews()runs over and over. What causes an Auto Layout feedback loop?// 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…A scrolling list drops frames and Instruments shows the time in Auto Layout. What do you check and what do you change?