'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.
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.
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 |


