Understanding SwiftUI Layout: A Step-by-Step Guide to Stacks and Child View Sizing

Introduction

SwiftUI uses a set of general rules to determine the size of a container view and its child views. These rules enable developers to create dynamic and flexible UIs that can adapt to different screen sizes and content types. Let’s delve into the specifics of how SwiftUI manages container and child view sizing through a step-by-step process.

Code Example

To illustrate how these principles apply in practice, consider the following SwiftUI code example:

    var body: some View {
        HStack {
            Text("Least priority")
                .lineLimit(2)
                .background(.white)
                .layoutPriority(-1)
            Text("Fourth priority")
                .lineLimit(2)
                .background(.blue)
            Text("Third priority")
                .lineLimit(2)
                .fixedSize()
                .background(.green)
            Text("Second priority")
                .background(.yellow)
                .layoutPriority(1)
            Text("First priority")
                .background(.yellow)
                .lineLimit(1)
                .layoutPriority(1)
        }
    }

Step 1: Determining the Container’s Frame

The container view (HStack or VStack) begins by determining its own frame based on the frame suggested by its parent view. This initial step sets the stage for deciding how much space is available for its child views.

Step 2: Selecting a Child View

The container view sorts its child views based on the following criteria, applied in their order of priority:

  1. Highest Priority: Views can be assigned priorities using the .layoutPriority modifier. By default, all views have a priority of 0. The container view prioritizes the view with the highest number, resorting to subsequent rules for views with equal priority.
  2. Most Restrictive Constraints: The child view with the most constraints affecting its layout is selected next. Constraints include parameters like frame size, position, padding, and modifiers such as .lineLimit, which limits the number of text lines.
  3. Smallest Size: If views have the same priority and number of constraints, the smallest one is chosen.

Step 3: Proposing a Size for the Selected Child View

The container proposes a size for the chosen child view. This suggested size is calculated by dividing the container’s size by the number of children that do not yet know their size, taking into account the space already occupied by other views.

Step 4: Child View Size Selection

The child view selects its size, which may or may not conform to the container’s suggestion, allowing for flexible and adaptive layouts.

Step 5: Recalculating Available Size

After a child view selects its size, the container subtracts this from its available space and recalculates the size it can offer to the remaining children, ensuring accurate sizing proposals for subsequent views.

Step 6: Iterative Child Selection

If lower-priority children remain, the process loops back to Step 2, repeating the sizing process until all child views have determined their sizes.

Step 7: Finalizing the Container Size

With all child views’ sizes known, the container can now calculate its own size, ensuring it adequately encompasses all its children.

Conclusion

This article provides a foundational understanding of how elements like HStack and VStack determine their sizes in SwiftUI, laying the groundwork for effective UI design. However, the best way to grasp the nuances of container and child view behavior is through hands-on practice. Dive in and experiment to truly master SwiftUI’s layout mechanics.

Written by

Dragos Resetnic

I have over 15 years of experience in software development, with a particular focus on the Apple platform. My expertise lies in developing iOS applications for a wide range of clients. Along the way, I've had the opportunity to present technical talks and write articles on iOS/Swift, sharing my knowledge and insights with the community.