iosswiftuilayout

How to create "constraint like" layout in SwiftUI


Coming from UIKit I find it struggling to achieve desired results in SwiftUI. I assume I am to caught in the constraint driven layout to get my head around the SwiftUI approach.

Assume the following layout:

enter image description here

Solving this using constraints in UIKit would be no problem. I am aware that "regular", "smaller" and "bigger" devices are no actual size classes. This is just for illustration.

But how would this be solved in SwiftUI?

The basic layout is of course straight forward:

VStack(spacing: 0) {
    VStack {
        TopView()
        Spacer()
        CenterView()
        Spacer()
        BottomView()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(.red)
    
    VStack {
        FillView()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(.green)
}

But how to achieve the min- and maxHeight of the top-container? Is it possible to do this without using a GeometryReader but only with relative values?

How to avoid shrinking the top container below a given min-height when the bottom container becomes to big due to its content?


Solution

  • I think you can get quite close to your requirements with a few small changes:

    VStack(spacing: 20) {
        TopView()
        CenterView()
            .frame(maxHeight: .infinity)
        BottomView()
    }
    
    VStack(spacing: 20) {
        // ...
    }
    .padding()
    .frame(maxWidth: .infinity, maxHeight: 400)
    .background(.red)
    
    FillView()
        .frame(maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
        .background(.green)
    

    Note that the height of all iPhones in landscape orientation is smaller than even the smallest iPhone in portrait orientation.

    Putting it all together:

    VStack(spacing: 0) {
        VStack(spacing: 20) {
            TopView()
            CenterView()
                .frame(maxHeight: .infinity)
            BottomView()
        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: 400)
        .background(.red)
    
        FillView()
            .frame(maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
            .background(.green)
    }
    

    Another way to distibute the space using ratios is to use a custom Layout. An example implementation which distributes the views horizontally by their weights can be found in the answer to SwiftUI How to Set Specific Width Ratios for Child Elements in an HStack (it was my answer).