4

I have a similar problem to this question (no answer yet): SwiftUI HStack with GeometryReader and paddings

In difference my goal is to align two views inside an HStack and where the left view gets 1/3 of the available width and the right view gets 2/3 of the available width.

Using GeometryReader inside the ChildView messes up the whole layout, because it fills up the height.

This is my example code:

struct ContentView: View {
    var body: some View {
        VStack {
            VStack(spacing: 5) {
                ChildView().background(Color.yellow.opacity(0.4))
                ChildView().background(Color.yellow.opacity(0.4))
                Spacer()
            }

            .padding()

            Spacer()

            Text("Some random Text")
        }
    }
}

struct ChildView: View {
    var body: some View {
        GeometryReader { geo in
            HStack {
                Text("Left")
                    .frame(width: geo.size.width * (1/3))
                Text("Right")
                    .frame(width: geo.size.width * (2/3))
                    .background(Color.red.opacity(0.4))
            }
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.green.opacity(0.4))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Which results in this:

enter image description here

Now If you would embed this view inside others views the layout is completely messed up:

e.g. inside a ScrollView

enter image description here

So how would one achieve the desired outcome of having a HStack-ChildView which fills up the space it gets and divides it (1/3, 2/3) between its two children?

EDIT

As described in the answer, I also forgot to add HStack(spacing: 0). Leaving this out is the reason for the right child container to overflow.

3
  • I can see in your code you want 2 ChildView() and one Text, Which I think you want they take 1/3 each vertically? while that ChildView() has a horizontal spacing of 1/3 and 2/3. Commented Mar 16, 2021 at 14:23
  • Inside the ScrollView, how tall are you expecting the ChildView elements to be? They lose the ability to calculate an intrinsic size because there's no height to constrain them. Commented Mar 16, 2021 at 16:23
  • SwiftPunk: I want to constraint the horizontal spacing. @jnpdx I would want the ChildViews / TextViews to behave like before, where they only required the height they needed. Commented Mar 17, 2021 at 7:34

1 Answer 1

1

You can create a custom PreferenceKey for the view size. Here is an example:

struct ViewSizeKey: PreferenceKey {
    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

Then, create a view which will calculate its size and assign it to the ViewSizeKey:

struct ViewGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear
                .preference(key: ViewSizeKey.self, value: geometry.size)
        }
    }
}

Now, you can use them in your ChildView (even if it's wrapped in a ScrollView):

struct ChildView: View {
    @State var viewSize: CGSize = .zero
    
    var body: some View {
        HStack(spacing: 0) { // no spacing between HStack items
            Text("Left")
                .frame(width: viewSize.width * (1 / 3))
            Text("Right")
                .frame(width: viewSize.width * (2 / 3))
                .background(Color.red.opacity(0.4))
        }
        .frame(minWidth: 0, maxWidth: .infinity)
        .background(Color.green.opacity(0.4))
        .background(ViewGeometry()) // calculate the view size
        .onPreferenceChange(ViewSizeKey.self) {
            viewSize = $0 // assign the size to `viewSize`
        }
                    
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for your answer. It actually solves the problem. I was hoping there would be an easier way, but still this is working!

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.