'How to size subviews in HStack by percentage without GeometryReader (SwiftUI)?

I've got a simple HStack with subviews inside. How can I tell the first subview to be 60% the size of the HStack without using a GeometryReader?

struct ContentView: View {
    var body: some View {
        HStack {
            Color.red.opacity(0.3)
            Color.brown.opacity(0.4)
            Color.yellow.opacity(0.6)
        }
    }
}

The code above makes each subview the same size. But I want the first one to be 60% regardless of it's content. In this example, it is a color, but it could be anything. The HStack is dynamic in size.

enter image description here

Edit: Why no GeometryReader?

When I want to place multiple of those HStacks inside a ScrollView, they overlap, because the GeometryReader's height is only 10 Point. As mentioned above, the Color views could be anything, so I used VStacks with cells in it that have dynamic heights.

struct ContentView: View {
    
    var body: some View {
        ScrollView(.vertical) {
            ProblematicView()
            ProblematicView()
        }
    }
}

struct ProblematicView: View {
    
    var body: some View {
        GeometryReader { geo in
            HStack(alignment: .top) {
                VStack {
                    Rectangle().frame(height: 20)
                    Rectangle().frame(height: 30)
                    Rectangle().frame(height: 20)
                    Rectangle().frame(height: 40)
                    Rectangle().frame(height: 20)
                }
                .foregroundColor(.red.opacity(0.3))
                .frame(width: geo.size.width * 0.6)
                .overlay(Text("60%").font(.largeTitle))
                
                VStack {
                    Rectangle().frame(height: 10)
                    Rectangle().frame(height: 30)
                    Rectangle().frame(height: 20)
                }
                .foregroundColor(.brown.opacity(0.4))
                .overlay(Text("20%").font(.largeTitle))
                
                VStack {
                    Rectangle().frame(height: 5)
                    Rectangle().frame(height: 10)
                    Rectangle().frame(height: 24)
                    Rectangle().frame(height: 10)
                    Rectangle().frame(height: 17)
                    Rectangle().frame(height: 13)
                    Rectangle().frame(height: 10)
                }
                .foregroundColor(.yellow.opacity(0.6))
                .overlay(Text("20%").font(.largeTitle))
            }
        }
        .border(.blue, width: 3.0)
    }
}

As you can see, the GeometryReader's frame is too small in height. It should be as high as the HStack. That causes the views to overlap.

enter image description here



Solution 1:[1]

I don't know the exact reason (might be a bug in GeometryReader), but placing the GeometryReader outside the ScrollView, and passing down its width makes your code behave as you expect.

struct ContentView: View {
    var body: some View {
        GeometryReader { geo in
            ScrollView {
                ProblematicView(geoWidth: geo.size.width)
                ProblematicView(geoWidth: geo.size.width)
            }
        }
        .border(.blue, width: 3.0)
    }
}

struct ProblematicView: View {
    let geoWidth: CGFloat
    
    var body: some View {
        // same code, but using geoWidth to compute the relative width

Result:

Solution 2:[2]

You can set by .frame & UIScreen.main.bounds.size.width * (your width ratio) calculation.

Example

struct ContentView: View {
    var body: some View {
        HStack {
            Color.red.opacity(0.3)
                .frame(width: UIScreen.main.bounds.size.width * 0.6, height: nil)
            Color.purple.opacity(0.4)
                .frame(width: UIScreen.main.bounds.size.width * 0.2, height: nil)
            Color.yellow.opacity(0.6)
                .frame(width: UIScreen.main.bounds.size.width * 0.2, height: nil)
        }
    }
}

Using GeometryReader

struct ContentView: View {
    var body: some View {
        GeometryReader { geo in
            HStack {
                Color.red.opacity(0.3)
                    .frame(width: geo.size.width * 0.6, height: nil)
                Color.brown.opacity(0.4)
                    .frame(width: geo.size.width * 0.2, height: nil)
                Color.yellow.opacity(0.6)
                    .frame(width: geo.size.width * 0.2, height: nil)
            }
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cristik
Solution 2