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:

  1. Proposal Phase - Parent proposes sizes to children
  2. 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)

  • LeadingCenterTrailing
  • Equal spacing between views

VStack (Vertical Distribution)

  • TopCenterBottom
  • 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)
}

SwiftUI Layout Diagram

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.