Understanding SwiftUI's Layout System
SwiftUI’s layout system can be confusing at first. Let’s demystify how views are sized and positioned.
The Two-Phase Layout Process
SwiftUI uses a two-phase layout system:
- Proposal Phase - Parent proposes sizes to children
- Resolution Phase - Children return their final sizes
struct ContentView: View {
var body: some View {
VStack {
Text("Hello")
Text("World")
}
.padding()
.background(Color.yellow)
}
}
Alignment vs. Distribution
HStack (Horizontal Distribution)
- Leading → Center → Trailing
- Equal spacing between views
VStack (Vertical Distribution)
- Top → Center → Bottom
- Equal spacing between views
Spacer and Flexible Space
HStack {
Text("Left")
Spacer() // Takes all available space
Text("Right")
}
GeometryReader Pitfalls
GeometryReader can cause layout loops if used incorrectly:
// ❌ Bad - causes infinite loop
GeometryReader { geometry in
Rectangle()
.frame(width: geometry.size.width / 2)
}
// ✅ Good - read once, use fixed values
GeometryReader { geometry in
let width = geometry.size.width / 2
Rectangle()
.frame(width: width)
}
Practical Example: Card Layout
struct CardView: View {
var title: String
var description: String
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(title)
.font(.headline)
Text(description)
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 4)
}
}
Understanding these principles will help you debug layout issues faster.