How willSizeThatFitsBlock, willLayoutSubviewsBlock, and didLayoutSubviewsBlock Operate in FrameLayoutKit

The willSizeThatFitsBlock, willLayoutSubviewsBlock, and didLayoutSubviewsBlock closures provide three deterministic lifecycle hooks in FrameLayoutKit that execute before sizing, before subview positioning, and after layout completion respectively, enabling precise intervention without subclassing.

FrameLayoutKit by kennic offers these optional callback properties on all FrameLayout-derived views to inject custom logic directly into the layout pipeline. Understanding exactly when each willSizeThatFitsBlock, willLayoutSubviewsBlock, and didLayoutSubviewsBlock fires—and what parameters they receive—allows you to modify constraints, adjust insets, or trigger animations at the precise moment needed.

Callback Lifecycle Overview

Each callback serves a distinct phase in the UIKit layout process. When assigned, these closures are stored as optional properties and invoked at specific points in FrameLayout.swift and its subclasses.

Callback Invocation Timing Parameters Primary Use Case
willSizeThatFitsBlock First line of sizeThatFits(_:ignoreHiddenView:) (FrameLayout, CGSize) Adjust incoming size constraints or log measurements before calculation begins
willLayoutSubviewsBlock Start of layoutSubviews(), before super.layoutSubviews() (FrameLayout) Modify edgeInsets, toggle visibility, or prepare auxiliary data before frame assignment
didLayoutSubviewsBlock Inside defer block at end of layoutSubviews(), after all frames are set (FrameLayout) Trigger animations, notify delegates, or clean up temporary state after positioning

All three properties default to nil and are declared as optional closure types in the source.

Implementation Details in FrameLayoutKit Source

The callbacks are defined in FrameLayout.swift and inherited by StackFrameLayout and ScrollStackView. The repository places the invocation points strategically to ensure deterministic execution order.

willSizeThatFitsBlock Invocation

In FrameLayoutKit/Classes/FrameLayout.swift, the willSizeThatFitsBlock fires immediately upon entering the sizing method, receiving the layout instance and the proposed size.

// FrameLayout.swift - sizeThatFits(_:ignoreHiddenView:)
willSizeThatFitsBlock?(self, size)      // Called before any visibility checks
if isEmpty && ignoreHiddenView { 
    // sizing logic continues...
}

This hook executes before the view measures its content and before any visibility checks, making it ideal for capping maximum widths or logging performance metrics.

willLayoutSubviewsBlock Invocation

The willLayoutSubviewsBlock triggers at the very beginning of layoutSubviews() in FrameLayout.swift, prior to calling super.layoutSubviews() or calculating any frames.

// FrameLayout.swift - layoutSubviews()
willLayoutSubviewsBlock?(self)          // Executed first
super.layoutSubviews()
// ... frame calculations follow

In StackFrameLayout.swift, this inherited behavior allows you to adjust stack-specific properties like spacing or alignment immediately before the layout engine positions subviews.

didLayoutSubviewsBlock Invocation

The didLayoutSubviewsBlock runs inside a defer block placed at the end of layoutSubviews(), guaranteeing execution after all frames are assigned and after optional skeleton-view handling completes.

// FrameLayout.swift - layoutSubviews()
willLayoutSubviewsBlock?(self)
super.layoutSubviews()
defer {
    // skeleton handling occurs here
    didLayoutSubviewsBlock?(self)       // Final hook after all layout work
}

StackFrameLayout.swift implements an identical defer pattern, while ScrollStackView.swift inherits this behavior directly from FrameLayout, ensuring consistent post-layout callbacks across all layout containers.

Practical Code Examples

These closures support both direct property assignment and a fluent chainable API defined in Extensions/FrameLayout+Chainable.swift and Extensions/ScrollStackView+Chainable.swift.

Logging Incoming Size Constraints

Capture the exact size passed to sizeThatFits for debugging adaptive layouts.

let layout = FrameLayout { layout in
    layout.willSizeThatFits { layout, targetSize in
        print("🔍 willSizeThatFits – target size:", targetSize)
    }
}

Adjusting Edge Insets Before Layout

Dynamically modify padding based on the view's current bounds before frames are calculated.

let stack = StackFrameLayout()
stack.willLayoutSubviews { layout in
    if layout.bounds.width > 300 {
        layout.edgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
    } else {
        layout.edgeInsets = .zero
    }
}

Post-Layout Animation Triggers

Fade in content only after frames are fully determined to prevent animation artifacts.

let scroll = ScrollStackView()
scroll.didLayoutSubviews { layout in
    UIView.animate(withDuration: 0.25) {
        layout.alpha = 1.0
    }
}
scroll.alpha = 0.0  // Start hidden until layout completes

Fluent Chainable API Usage

The chainable methods return self, enabling compact configuration while assigning the underlying blocks.

let card = FrameLayout()
    .willSizeThatFits { layout, size in
        layout.maxWidth = min(layout.maxWidth, 200)
    }
    .willLayoutSubviews { layout in
        layout.translationX = 10
    }
    .didLayoutSubviews { layout in
        print("✅ Layout done – final frame:", layout.frame)
    }

Summary

  • willSizeThatFitsBlock executes at the start of sizeThatFits(_:ignoreHiddenView:) in FrameLayout.swift, receiving the target size to adjust sizing logic before measurement occurs.
  • willLayoutSubviewsBlock fires at the beginning of layoutSubviews(), before super.layoutSubviews() runs, allowing last-minute configuration of insets or visibility states.
  • didLayoutSubviewsBlock runs inside a defer block after all frames are assigned, providing a safe hook for animations and post-layout notifications.
  • All three callbacks are inherited by StackFrameLayout and ScrollStackView, with chainable convenience methods available in the Extensions/ directory.

Frequently Asked Questions

When should I use willSizeThatFitsBlock instead of willLayoutSubviewsBlock?

Use willSizeThatFitsBlock when you need to modify size constraints or inspect the proposed CGSize before the view calculates its content dimensions. Use willLayoutSubviewsBlock when adjusting properties that affect frame positioning—such as edgeInsets, translationX, or subview visibility—immediately before the layout engine assigns frames.

Are these callbacks available in StackFrameLayout and ScrollStackView?

Yes. Both StackFrameLayout and ScrollStackView inherit from FrameLayout, so they possess the same willSizeThatFitsBlock, willLayoutSubviewsBlock, and didLayoutSubviewsBlock properties. StackFrameLayout.swift additionally ensures didLayoutSubviewsBlock fires in its overridden layoutSubviews() method via the same defer pattern.

How do the chainable methods differ from direct property assignment?

The chainable methods defined in Extensions/FrameLayout+Chainable.swift and Extensions/ScrollStackView+Chainable.swift are simple wrappers that assign the closure to the underlying block property while returning self to enable fluent syntax. Functionally, layout.willLayoutSubviews { ... } is identical to layout.willLayoutSubviewsBlock = { ... }, but the chainable style improves readability when configuring multiple callbacks.

Can I safely modify view frames inside didLayoutSubviewsBlock?

While you can modify frames inside didLayoutSubviewsBlock, doing so may trigger another layout pass if the changes affect layout constraints. This callback executes after the layout engine finishes, making it ideal for animations, logging, or notifying delegates, but be cautious about mutating geometry to avoid performance penalties from redundant layout cycles.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →