← All topics/UIKit & SwiftUI internals

Question 1

View bounds and layout timing

Where do common mistakes happen when reading view bounds or forcing layout too early?

Follow-ups

  • When is viewDidLoad the wrong place to read view.bounds?

Answer outline

Most of these bugs come from reading geometry before the system has finished a layout pass. Three patterns cover nearly all of them:

  1. 1.Bounds read too early: in viewDidLoad, view.bounds is usually not final, because layout for the current orientation, safe area, and container size hasn't happened yet. Layer frames or clipping paths set from those values stay wrong until something forces a relayout.
  2. 2.Forced layout: sprinkling layoutIfNeeded() around to force frames into place when constraints are ambiguous or incomplete hides the real problem and adds extra layout passes. Fix the constraints instead, and remember that forcing layout in viewDidLoad still runs against a size that may not be final.
  3. 3.One-time safe area reads: safeAreaInsets change after viewDidLoad when bars show or hide, on rotation, in split view, or inside a custom container, so don't cache a single read at load time. For keyboard avoidance, use view.keyboardLayoutGuide (iOS 15 and later).

Prefer Auto Layout constraints wherever you can. They stay correct across rotations, safe area changes, and container transitions without any manual pixel work.

Principles

  • Treat viewDidLoad as the place for wiring and subview creation, because final frames aren't known yet.
  • Read geometry and position layers in viewDidLayoutSubviews, after calling super, when you need real pixels.
  • layoutIfNeeded() is a timing tool for animating constraint changes, and it never repairs an ambiguous layout.
  • safeAreaInsets aren't stable at load time, so respond to changes instead of reading them once.
Geometry-dependent work in viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // bounds now match the container and safe area for this pass
    gradientLayer.frame = view.bounds
}

Change the constant, then animate the layout pass so the view moves smoothly:

Animating a constraint change with layoutIfNeeded()
heightConstraint.constant = 200
UIView.animate(withDuration: 0.3) {
    self.view.layoutIfNeeded()  // animates the constraint change
}

Follow-up angles

  • In SwiftUI, don't assume the size you see in onAppear is final. Use GeometryReader or onGeometryChange when layout depends on the proposed size.