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.

Ali
  • 761
  • 1
  • 5
  • 24
  • 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. – ios coder Mar 16 '21 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. – jnpdx Mar 16 '21 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. – Ali Mar 17 '21 at 07:34

1 Answers1

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`
        }
                    
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Thank you for your answer. It actually solves the problem. I was hoping there would be an easier way, but still this is working! – Ali Mar 18 '21 at 07:15