Search code examples
iosswiftswiftui

Buttons in SwiftUI List ForEach view trigger even when not "tapped"?


I have the following code:

struct ButtonTapTest: View {
    
    let items = [1, 2, 3]
    
    var body: some View {
        
        List {
            ForEach(items, id:\.self) { item in
                CellTestView()
            }
        }
        
    }
}


struct CellTestView:View {
    
    
    var body: some View {
        
        VStack {
            
            Button {
                print("TOP")
            } label: {
                Image(systemName: "play.fill")
                    .font(.system(size: 40))
                    .foregroundColor(.red)
            }
            .border(.red)
            
            Spacer()
            
            Button {
                print("BOTTOM")
                
            } label: {
                Image(systemName: "play")
                    .font(.system(size: 40))
                    .foregroundColor(.red)
            }
            .border(.yellow)
            
        }
        
    }
    
}

Creates the following screen:

enter image description here

Problem:

Both button actions get triggered in the cell regardless of where I tap on the CellTestView. I want the individual button actions to trigger separately, each only when its button gets tapped, and not when I tap outside anywhere else on the cell.

You can see in the gif below that it does not matter where I tap on the CellTestView, both button actions get trigger regardless where on the view I tap, and both "TOP" and "BOTTOM" logs trigger at the same time.

How can I fix this so the two buttons in the cell receive the tap independently and only when the tap is inside the related button?

enter image description here


Solution

  • Whenever you have multiple buttons in a list row, you need to manually set the button style to .borderless or .plain. This is because buttons “adapt” to their context. According to the documentation:

    If you create a button inside a container, like a List, the style resolves to the recommended style for buttons inside that container for that specific platform.

    So when your button is in a List, its tap target extends to fill the row and you get a highlight animation. SwiftUI isn’t smart enough to stop this side effect when you have more than 2 buttons, so you need to set buttonStyle manually.

    CellTestView()
        .buttonStyle(.borderless)
    

    Result:

    Tapping top and bottom button results in separate print statements