0

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:

  • On devices with a "regular" screen the horizontal space should be split 1:1 into a top and bottom part.

  • The top container hold three views with fixed height which should be positioned at the top, center and bottom of the container.

  • The three views have some minimum spacing, e.g. 20px. If enough space is available the spacing can be more but never below 20px.

  • On smaller devices the top container can shrink up to a certain level. But never so small, that the fixed width of the subviews and their spacing would be violated. In this case the ratio between the top and bottom container would not be 1:1 anymore but e.g. 2:3.

  • On larger devices like an iPad the top container would only grow up to some max. height. In this case the ratio between top and bottom container would also not be 1:1 any more.

  • The bottom container view holds only one view which should always fill the available space.

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?

2
  • SwiftUI.Layout can do this Commented Dec 10, 2024 at 16:23
  • What constraints requires the top view to shrink? I don't see why it needs to shrink on smaller devices or enlarge on larger devices. Is there some other unstated requirement? When exactly should the ratio be 1:1? In other words, what is a "regular" screen? Commented Dec 10, 2024 at 19:24

1 Answer 1

1

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

  • The VStack with the Spacer will add some (default) spacing in-between the views it contains, so this includes some additional spacing around the Spacer. In order to enforce the 20 points minimum spacing, I would suggest using spacing: 20 on the VStack, then remove the Spacer and set maxHeight: .infinity on CenterView:
VStack(spacing: 20) {
    TopView()
    CenterView()
        .frame(maxHeight: .infinity)
    BottomView()
}
  • For the case of the larger screens, set a finite maxHeight on the upper VStack, instead of .infinity:
VStack(spacing: 20) {
    // ...
}
.padding()
.frame(maxWidth: .infinity, maxHeight: 400)
.background(.red)
  • The requirement for the smaller screens is the only one that is not so easy to fulfil exactly. You will presumably have a threshold height, below which the layout switches from a ratio of 1:1 to a ratio of 2:3. You can achieve something similar by setting a minimum height on FillView, computed from the threshold height:
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).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.