How bindingViews and lazyBindingViews Synchronize Views with a Target View's Frame in FrameLayoutKit

bindingViews and lazyBindingViews automatically synchronize the frames of auxiliary views to match a target view's dimensions by evaluating a static array or dynamic closure during every layoutSubviews() pass.

FrameLayoutKit provides a powerful mechanism to keep multiple views visually aligned with a primary target view. Through the bindingViews, lazyBindingViews, and bindingEdgeInsets properties, the library ensures that dependent views automatically resize and reposition whenever the target view's frame changes. This synchronization happens in real-time during the layout cycle, creating a "live mirror" effect without manual frame calculations.

Understanding the Binding Properties

FrameLayout exposes three key properties in FrameLayoutKit/Classes/FrameLayout.swift that control view synchronization:

Property Type Purpose
bindingViews [UIView]? A static array of views bound to the target view during every layout pass.
bindingEdgeInsets UIEdgeInsets Insets applied to the target view's frame before copying to bound views.
lazyBindingViews (() -> [UIView?]?)? A closure evaluated on every layout pass, enabling dynamic view binding.

The bindingEdgeInsets property allows you to create padding effects. For example, setting UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) causes bound views to be 10 points smaller on each side than the target view.

How Frame Synchronization Works

The synchronization mechanism triggers automatically during the view layout cycle. In FrameLayout.swift (lines 75-95), the layoutSubviews() method finishes positioning the targetView and then invokes:

bindViews(to: targetView)

The bindViews(to:) implementation performs three critical steps:

1. Collect Views to Bind

var bindViews = bindingViews ?? []
if let views = lazyBindingViews?() { 
    bindViews.append(contentsOf: views.compactMap { $0 }) 
}

This merges the static bindingViews array with the dynamic results from lazyBindingViews. The closure is executed on every layout pass, allowing runtime changes to the bound view set.

2. Compute the Source Frame

let targetFrame = targetView.frame.inset(by: bindingEdgeInsets)

The target view's current frame is adjusted by the specified insets. This computed rectangle serves as the source of truth for all bound views.

3. Copy Frame to Bound Views

The implementation handles three hierarchy scenarios:

  • Same superview: Sets frame to (0,0,size) relative to shared parent
  • Different superviews: Converts the frame from target's superview to bound view's superview using coordinate conversion
  • Direct assignment: Uses the frame directly when no conversion is needed

This ensures bound views occupy the same visual rectangle as the target view, regardless of their position in the view hierarchy.

Static vs Dynamic Binding

FrameLayoutKit offers two approaches depending on whether your bound views are fixed or change at runtime.

Static Binding with bindingViews

Use bindingViews when you know at initialization which views must synchronize:

let avatar = UIImageView(image: UIImage(named: "avatar"))
let nameLabel = UILabel()
let profileLayout = FrameLayout(targetView: avatar)

// Keep the label sized exactly like the avatar
profileLayout.bindingViews = [nameLabel]
profileLayout.bindingEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

Dynamic Binding with lazyBindingViews

Use lazyBindingViews when the set of views changes based on state or user interaction:

var hintViews: [UILabel] = []

let hintLayout = FrameLayout(targetView: avatar)
hintLayout.lazyBindingViews = { [weak hintLayout] in
    // The closure is executed on each layout pass
    return hintViews.map { $0 as UIView? }
}

// Later you can add/remove hints and the layout will update automatically
let hint = UILabel()
hint.text = "Tap the avatar"
hintViews.append(hint)

The closure-based approach ensures that adding or removing views from hintViews automatically reflects in the next layout cycle without reconfiguring the FrameLayout instance.

Practical Implementation Examples

Chainable Configuration

FrameLayoutKit supports chainable setters via FrameLayout+Chainable.swift:

let layout = FrameLayout(targetView: imageView)
    .bindingViews([overlayView, shadowView])
    .bindingEdgeInsets(UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4))

Coordinate Space Conversion

When bound views exist in different superviews, FrameLayout automatically handles conversion. This is particularly useful in complex hierarchies:

// targetView is in containerA
// badgeView is in containerB (sibling of containerA)
let frameLayout = FrameLayout(targetView: targetView)
frameLayout.bindingViews = [badgeView]
// FrameLayout converts coordinates between containerA and containerB automatically

Summary

  • bindingViews provides static array-based synchronization for fixed sets of views.
  • lazyBindingViews enables dynamic runtime binding through a closure evaluated on every layout pass.
  • bindingEdgeInsets allows padding adjustments before frame copying.
  • The synchronization occurs in bindViews(to:) called from layoutSubviews() in FrameLayout.swift.
  • FrameLayout handles same-superview and different-superview scenarios automatically through coordinate conversion.

Frequently Asked Questions

What is the difference between bindingViews and lazyBindingViews?

bindingViews accepts a static array of UIView objects that remains constant unless you manually reassign the property. lazyBindingViews accepts a closure that returns an array of optional views and executes during every layoutSubviews() cycle. Use bindingViews for fixed relationships and lazyBindingViews when the set of bound views changes dynamically based on application state.

How do bindingEdgeInsets affect the synchronized frame?

The bindingEdgeInsets property insets the target view's frame before copying it to bound views. For example, setting UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) creates a 10-point padding, causing bound views to be smaller than the target view by 20 points in both dimensions (10 on each side). This allows bound views to act as borders, backgrounds, or inset overlays.

Does FrameLayoutKit handle views in different superviews?

Yes. During the bindViews(to:) execution, FrameLayoutKit checks whether the bound view shares the same superview as the target view. If the superviews differ, the implementation converts the target frame from the target's coordinate space to the bound view's coordinate space using UIKit's coordinate conversion methods. This ensures bound views occupy the same visual rectangle regardless of their position in the view hierarchy.

When should I use lazyBindingViews over bindingViews?

Use lazyBindingViews when the set of views requiring synchronization is not known at initialization time or changes during the view's lifecycle. Common scenarios include: dynamically adding hint labels based on user interaction, binding to views created in response to data changes, or managing views that may be nil or deallocated and recreated. The closure-based approach ensures FrameLayoutKit evaluates the current state on every layout pass without requiring manual property updates.

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 →