How to Nest Multiple StackFrameLayouts to Build Complex Layouts in FrameLayoutKit
FrameLayoutKit enables complex UI hierarchies by allowing HStackLayout, VStackLayout, and ZStackLayout instances to be nested recursively inside one another, treating each child stack as a standard FrameLayout that participates in the parent stack's layout calculations via the add(_:) method in StackFrameLayout.swift.
The kennic/FrameLayoutKit library provides a declarative layout system for iOS and macOS that simplifies view construction through composable stacks. When you nest multiple StackFrameLayouts, you create sophisticated interface structures—such as vertical lists containing horizontal rows or overlay groups—while maintaining clean, readable source code that scales with interface complexity.
The Inheritance Architecture Enabling Nesting
FrameLayoutKit's stack types—HStackLayout, VStackLayout, and ZStackLayout—are all subclasses of StackFrameLayout, which inherits from the base FrameLayout class defined in FrameLayout.swift. Because every stack is ultimately a FrameLayout, the parent stack can treat nested stacks exactly like standard views. This polymorphic design allows the add(_:) method to accept any FrameLayout subclass, storing it in the internal frameLayouts array and adding it as a child view via addSubview(_:).
How StackFrameLayout.swift Detects and Stores Child Stacks
The nesting logic resides in StackFrameLayout.swift. When you call add(_:) to insert a sub-stack, the implementation performs a type check to determine if the supplied object is an existing FrameLayout instance:
if let frameLayout = view as? FrameLayout, frameLayout.superview == nil {
applyCommonAttributes(to: frameLayout)
frameLayouts.append(frameLayout)
addSubview(frameLayout)
return frameLayout
}
This check ensures the child is unowned (superview == nil) before appending it to frameLayouts and applying common attributes via applyCommonAttributes(to:). By returning the frameLayout directly rather than wrapping it, the library preserves the full layout capabilities of nested stacks.
Recursive Layout Calculation
During the layout phase, each stack executes layoutSubviews() to measure and position its children. The method calls sizeThatFits and layoutSubviews on every child in the frameLayouts array. If a child is itself a StackFrameLayout, the same measurement logic runs recursively, creating a depth-first layout pass through the entire tree. This recursive approach ensures that nested stacks calculate their internal sizes before parents distribute available space.
Key Behaviors for Complex Nesting
Several mechanisms in StackFrameLayout.swift and FrameLayout.swift work together to support deep nesting:
- Common attribute propagation: The
applyCommonAttributes(to:)method copies debugging overlays, skeleton loading states, and caching behaviors from parent to child stacks automatically. - Flexible frame distribution: When
isFlexibleis true, nested stacks participate in the parent's space distribution usingflexibleRatio, allowing a nested stack to expand and fill remaining dimensions. - Justification and distribution: Parent stacks control alignment via
distributionandisJustifiedproperties, positioning nested stacks according to the parent's layout rules regardless of the child's internal complexity. - Overlapped mode: Setting
isOverlappedto true (used byZStackLayout) stacks children on top of each other, enabling overlay layouts where inner stacks sit on shared layers.
Practical Implementation: Building Deep Hierarchies
FrameLayoutKit provides two syntax styles for declaring nested structures: a trailing-closure DSL (defined in StackFrameLayout+DSL.swift) and a chainable builder pattern (defined in StackFrameLayout+Chainable.swift).
DSL Syntax Example
import FrameLayoutKit
import UIKit
// MARK: - Example 1 – Vertical list of horizontal rows
let root = VStackLayout { vStack in
// Row 1
vStack.add(
HStackLayout { hStack in
hStack.add(UILabel()) // label 1
.fixedWidth(80)
hStack.add(UIButton()) // button 1
.fixedWidth(120)
}
.spacing(8) // spacing inside the row
)
// Row 2 (flexible height)
vStack.add(
HStackLayout { hStack in
hStack.add(UILabel())
.flexible() // takes remaining vertical space
hStack.add(UIButton())
.fixedWidth(120)
}
.spacing(8)
)
// Row 3 – overlay badge using ZStack
vStack.add(
ZStackLayout { zStack in
// Background view (full width)
zStack.add(UIView())
.fixedHeight(40)
.backgroundColor(.lightGray)
// Badge on top‑right corner
zStack.add(UILabel())
.fixedSize(CGSize(width: 24, height: 24))
.alignment(.top, .right) // custom extension for alignment
}
.spacing(0)
)
}
.root?.backgroundColor = .white
Chainable Syntax Example
// MARK: - Example 2 – Chainable syntax (same layout as above, shorter)
let root2 = VStackLayout()
.spacing(12)
.add(
HStackLayout()
.spacing(8)
.add(UILabel()).fixedWidth(80)
.add(UIButton()).fixedWidth(120)
)
.add(
HStackLayout()
.spacing(8)
.add(UILabel()).flexible()
.add(UIButton()).fixedWidth(120)
)
.add(
ZStackLayout()
.add(UIView()).fixedHeight(40).backgroundColor(.lightGray)
.add(UILabel()).fixedSize(CGSize(width: 24, height: 24)).alignment(.top, .right)
)
Both examples produce the same visual hierarchy:
VStack
├─ HStack (row 1)
│ ├─ UILabel
│ └─ UIButton
├─ HStack (row 2, flexible height)
│ ├─ UILabel (flexible)
│ └─ UIButton
└─ ZStack (row 3)
├─ UIView (background)
└─ UILabel (badge overlay)
Summary
- StackFrameLayouts nest because they inherit from
FrameLayout, allowing type-safe insertion viaadd(_:)inStackFrameLayout.swift. - The
frameLayoutsarray stores children, andapplyCommonAttributes(to:)propagates settings down the tree. - Layout calculates recursively through
layoutSubviews()andsizeThatFits, with parent stacks determining final positions. - Flexible ratios, distribution rules, and overlapped modes give nested stacks adaptive sizing and positioning capabilities.
- DSL and chainable syntax in
StackFrameLayout+DSL.swiftandStackFrameLayout+Chainable.swiftprovide ergonomic APIs for deep hierarchies.
Frequently Asked Questions
Can I nest a ZStackLayout inside an HStackLayout or VStackLayout?
Yes. Because ZStackLayout inherits from StackFrameLayout, which inherits from FrameLayout, it can be added to any parent stack using the add(_:) method. The ZStack will be measured and positioned according to the parent stack's distribution rules while maintaining its own overlapped layout behavior for its children.
How deep can I nest StackFrameLayouts without performance issues?
There is no artificial limit enforced by the library. Layout performance depends on the complexity of the view hierarchy and the cost of sizeThatFits calculations for leaf views. Since FrameLayoutKit uses a recursive layout pass that runs on the main thread, excessively deep trees (dozens of levels) may impact scroll performance, but typical interface depths of 3-10 levels execute efficiently.
Do nested stacks automatically inherit spacing and alignment from their parents?
Nested stacks do not automatically inherit layout-specific properties like spacing or distribution. However, they do inherit common attributes—including debugging overlays, skeleton loading states, and caching behaviors—via the applyCommonAttributes(to:) method called during add(_:). Each stack maintains independent control over its own internal layout parameters.
What is the difference between adding a raw UIView versus adding a StackFrameLayout as a child?
When you add a raw UIView, StackFrameLayout wraps it in a FrameLayout instance internally. When you add an existing StackFrameLayout (or HStackLayout/VStackLayout/ZStackLayout), the parent recognizes it via the as? FrameLayout type check and appends it directly to frameLayouts without wrapping, preserving the child's layout logic and avoiding unnecessary container overhead.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →