skip to Main Content

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

2

Answers


  1. 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)
                }
            }
        }
    }
    
    Login or Signup to reply.
  2. 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:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search