Ali
Ali

Reputation: 846

SwiftUI How to align child views inside HStack

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.

Upvotes: 4

Views: 1509

Answers (1)

pawello2222
pawello2222

Reputation: 54641

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`
        }
                    
    }
}

Upvotes: 1

Related Questions