SwiftUI: Multiple Buttons in a Single List Row
March 20, 2024 12:34pmIn SwiftUI, we love Lists. We love Lists so much because we simply must love Lists because SwiftUI loves Lists and, really, if you've spent any time in SwiftUI you know you're better off just learning to love what it loves.
So, sometimes we put a lot of stuff in a List. You know, really load it up. Maybe we throw in some of our custom views from elsewhere. And this is the part where I usually get caught up and have to re-realize the same thing again. I'll be tapping or clicking around while testing and notice that one button is triggering the action of another. Buttons within my list are all of a sudden unreliable and fire seemingly random bits of functionality.
That is because a list row itself functions as a tap target, so each button within in it does not intercept its own taps. So, the row instead funnels all those taps to one of the Buttons, seemingly at random (or maybe there's an order to it. Don't @ me, I don't really care.) Luckily the workaround is simple: Just set a button style. Any style, just as long as it's not DefaultButtonStyle
.
Here's an example where we have three Buttons in the same List row. Each Button sets a different value to a @State
variable which defines the color of elements within another of the List's rows.
import SwiftUI
struct ContentView: View {
@State private var brainsColor = Color.primary
var body: some View {
List {
Section {
VStack {
Image(systemName: "brain.fill")
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .center)
Text("BRAINS!")
.tracking(2)
.font(.custom("Silkscreen-Regular", size: 44))
}
.foregroundStyle(brainsColor)
}
Section("Brain Color") {
HStack(spacing: 44) {
Button {
brainsColor = .yellow
} label: {
Circle()
.foregroundStyle(.yellow)
.frame(width: 44)
}
Button {
brainsColor = .indigo
} label: {
Circle()
.foregroundStyle(.indigo)
.frame(width: 44)
}
Button {
brainsColor = .mint
} label: {
Circle()
.foregroundStyle(.mint)
.frame(width: 44)
}
}
.frame(maxWidth: .infinity, alignment: .center)
}
}
}
}
As implemented, only one color gets set (the last one) no matter which button you click. However, simply adding an explicit button style to each one allows them to function as expected.
Button {
brainsColor = .indigo
} label: {
Circle()
.foregroundStyle(.indigo)
.frame(width: 44)
}
.buttonStyle(PlainButtonStyle()) // OK, List, does this make you happy?!?
Weird!