I want to have a grid of buttons. The buttons will take as much horizontal space as available and will divide it uniformly. Additionally, the height of all the buttons should be the same based on the tallest one. Somehow I am not able to make these requirements to work together. Here for example, the height of each row is the same, but the heights between rows are different.
struct ContentView: View {
let texts = [["Short", "A bit longer text", "bbb"],
["a", "A bit longer text", "This is a very long text that might not fit in one line"],
["a", "b", "c"]
]
var body: some View {
VStack {
ForEach(texts, id: \.self) { row in
HStack {
ForEach(row, id: \.self) { text in
Button{ } label: {
Text(text)
.foregroundStyle(.white)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.red)
.clipShape(Capsule())
}
}
}
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}
#Preview {
ContentView()
}
I tried experimenting more, but nothing works and I have no intuition on how to progressively make the UI look more as I intend it to be. Every small change breaks everything in a big way.
One way to solve this is to establish the size of the footprint for the largest text label, then show the label of each button as an overlay over a (hidden) footprint.
The footprint can be found using a ZStack
with all the strings layered on top of each other. The size of the ZStack
is determined by the size of the largest text string:
private var footprint: some View {
ZStack {
ForEach(Array(texts.enumerated()), id: \.offset) { offset, row in
ForEach(Array(row.enumerated()), id: \.offset) { offset, text in
Text(text)
}
}
}
.hidden()
}
After that, you can use a VStack
in combination with an HStack
to put the grid together, like you were doing before, or you can just use a Grid
:
var body: some View {
Grid {
ForEach(Array(texts.enumerated()), id: \.offset) { offset, row in
GridRow {
ForEach(Array(row.enumerated()), id: \.offset) { offset, text in
Button {} label: {
footprint
.overlay { Text(text) }
.padding()
.foregroundStyle(.white)
.background {
Capsule().fill(.red)
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
If you don't want the buttons to be so spread out, just remove the .frame
at the end.