SwiftUI-like structural layout for existing UIKit and SnapKit projects — without migration or extra container UIViews.
StructuredLayout adds Row, Column, HStack, VStack, ZStack, and Spacer to UIKit. It compiles a declarative layout tree into native Auto Layout constraints and remains fully interoperable with SnapKit.
- Designed for existing projects that already use SnapKit; adopt it screen by screen.
- In the repository's representative Profile Card, layout code drops from 48 to 29 lines — 39.6% fewer.
Row/Column/HStack/VStackmake source structure mirror the UI hierarchy.- Layout nodes use
UILayoutGuide; they do not add container UIViews. - It does not replace SnapKit. Keep
snp.makeConstraintsfor special relationships. - The core package has zero third-party dependencies.
- Measured installation overhead versus SnapKit is approximately 0.076 ms per representative card in the documented Debug simulator benchmark.
The line-count result belongs to the included example, not every possible screen. Benchmark results are snapshots, not cross-device guarantees.
Both implementations render the same Profile Card with the same 10 business UIViews and no layout-only container UIViews.
| Implementation | Layout LOC | Manual addSubview |
Result |
|---|---|---|---|
| SnapKit only | 48 | Required | Baseline |
| StructuredLayout | 29 | Not required | 39.6% fewer lines |
The counted regions are marked with comparison-layout-start/end in both source files. Shared view creation, typography, colors, and content live in ProfileCardElements.swift and are excluded from both totals.
Choose File → Add Package Dependencies and enter:
https://github.com/swifter09/StructuredLayout.git
.package(
url: "https://github.com/swifter09/StructuredLayout.git",
from: "0.1.5"
)import StructuredLayout
import UIKit
final class ProfileView: UIView {
private let avatarView = UIImageView()
private let nameLabel = UILabel()
private let detailLabel = UILabel()
private let actionButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
sl.layout {
HStack(spacing: 12, alignment: .center) {
avatarView.size(48)
VStack(spacing: 4) {
nameLabel
detailLabel
}
.expand()
actionButton
}
.padding(16)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}No manual addSubview calls are required. HStack and VStack are aliases for Row and Column; both naming styles share the same layout nodes and constraint compiler.
Use StructuredLayout for primary structure and SnapKit for exceptional relationships:
sl.layout {
VStack {
headerView
contentView.expand()
footerView
}
}
floatingButton.snp.makeConstraints {
$0.trailing.bottom.equalTo(safeAreaLayoutGuide).inset(16)
}Avoid giving the same view conflicting size or position constraints through both systems.
| StructuredLayout | SwiftUI concept |
|---|---|
Row / HStack |
HStack |
Column / VStack |
VStack |
ZStack |
ZStack |
Spacer |
Spacer |
.padding(), .size(), .expand() |
View modifiers |
Result Builder if / for |
ViewBuilder conditions and collections |
StructuredLayout is not a SwiftUI runtime. It operates on existing UIView instances and installs Auto Layout constraints once. It has no value-type View, state-driven rendering, tree diffing, Environment, or View Identity.
The accurate positioning is: a SwiftUI-like structural layout DSL for UIKit, not a reimplementation of SwiftUI.
view.sl.layout(pinTo: .edges) { ... }
view.sl.layout(pinTo: .safeArea) { ... }
view.sl.layout(pinTo: .layoutMargins) { ... }
view.sl.layout(pinTo: .readableContent) { ... }
view.sl.layout(pinTo: .guide(customGuide)) { ... }pinTo selects the outer boundary of the layout tree. .padding(...) controls its internal spacing.
Static layouts do not need to retain the returned handle:
view.sl.layout {
VStack { contentView }
}Retain it when you need lifecycle control:
private var layoutHandle: LayoutHandle?
layoutHandle = view.sl.layout {
VStack { contentView }
}
layoutHandle?.deactivate()
layoutHandle?.activate()
layoutHandle?.uninstall()uninstall() removes constraints and layout guides created by that installation while preserving business UIViews by default. It is terminal; install a new tree when another layout is needed.
The isolated Benchmark package compares Native Auto Layout, SnapKit 6.0.0, and StructuredLayout while keeping the core package dependency-free.
| Implementation | 100 cards | Per card | Physical memory delta |
|---|---|---|---|
| Native Auto Layout | 39.5 ms | 0.395 ms | 495 KB |
| SnapKit 6.0.0 | 48.7 ms | 0.487 ms | 485 KB |
| StructuredLayout | 56.4 ms | 0.564 ms | 482 KB |
In this Debug / iPhone 17 Simulator / iOS 26.5 snapshot, StructuredLayout adds approximately 0.076 ms per card versus SnapKit. Memory differences are within measurement noise.
See Benchmarks/README.md for scope, commands, and interpretation.
Row/HStackColumn/VStackZStackSpacerpaddingwidth,height,sizeminWidth,maxWidth,minHeight,maxHeightaspectRatioexpandnatural,equalSize- Result Builder conditions and loops
LayoutHandle
Open StructuredLayoutExample.xcodeproj and run it. The segmented control switches between the StructuredLayout and SnapKit implementations of the same UI.
See ROADMAP.md for planned exploration around Grid and wrapping, responsive updates, constraint animation, and collection/table-cell reuse guidance.
expand()lowers the main-axis hugging priority of leaf UIViews; it is not full CSSflex-grow.- No wrapping, Grid, responsive state, or layout-tree diffing yet.
- UIViews in one installed tree must be direct subviews of the installation container.
- Install layouts once during initialization or
setupUI; do not reinstall inside every cellconfigurecall. - APIs may evolve before 1.0.
Yoga calculates frames. StructuredLayout generates Auto Layout constraints so it can interoperate naturally with SnapKit, safe areas, intrinsic content sizes, and constraint animation. The MVP intentionally keeps one layout system.
Issues, API feedback, and focused pull requests are welcome. Real-world UIKit/SnapKit migration examples are especially valuable before 1.0.
StructuredLayout is available under the MIT license. See LICENSE.


