SwiftUI - Multiple Buttons in a List row

IosSwiftSwiftui

Ios Problem Overview


Say I have a List and two buttons in one row, how can I distinguish which button is tapped without the entire row highlighting?

For this sample code, when any one of the buttons in the row is tapped, both button's action callbacks are invoked.

// a simple list with just one row
List {

    // both buttons in a HStack so that they appear in a single row
    HStack {
        Button {
            print("button 1 tapped")
        } label: {
            Text("One")
        }
            
        Button {
            print("button 2 tapped")
        } label: {
            Text("Two")
        }
    }
}

When only one of buttons is tapped once, I see the callbacks for both buttons being called, which is not what I want:

button 1 tapped
button 2 tapped

Ios Solutions


Solution 1 - Ios

You need to use BorderlessButtonStyle() or PlainButtonStyle().

    List([1, 2, 3], id: \.self) { row in
        HStack {
            Button(action: { print("Button at \(row)") }) {
                Text("Row: \(row) Name: A")
            }
            .buttonStyle(BorderlessButtonStyle())
            
            Button(action: { print("Button at \(row)") }) {
                Text("Row: \(row) Name: B")
            }
            .buttonStyle(PlainButtonStyle())
        }
    }

Solution 2 - Ios

Seems to be a specific issue concerning Button when contained in a List row.

Workaround:

List {
  HStack {
    Text("One").onTapGesture { print("One") }
    Text("Two").onTapGesture { print("Two") }
  }
}

This yields the desired output.

You can also use a Group instead of Text to have a sophisticated design for the "buttons".

Solution 3 - Ios

One of the differences with SwiftUI is that you are not creating specific instances of, for example UIButton, because you might be in a Mac app. With SwiftUI, you are requesting a button type thing.

In this case since you are in a list row, the system gives you a full size, tap anywhere to trigger the action, button. And since you've added two of them, both are triggered when you tap anywhere.

You can add two separate Views and give them a .onTapGesture to have them act essentially as buttons, but you would lose the tap flash of the cell row and any other automatic button like features SwiftUI would give.

List {
    HStack {
        Text("One").onTapGesture {
            print("Button 1 tapped")
        }

        Spacer()

        Text("Two").onTapGesture {
            print("Button 2 tapped")
        }
    }
}

Solution 4 - Ios

You need to create your own ButtonStyle:

  struct MyButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
      configuration.label
        .foregroundColor(.accentColor)
        .opacity(configuration.isPressed ? 0.5 : 1.0)
    }
  }

  struct IdentifiableString: Identifiable {
    let text: String
    var id: String { text }
  }

  struct Test: View {
    var body: some View {
      List([
        IdentifiableString(text: "Line 1"),
        IdentifiableString(text: "Line 2"),
      ]) {
        item in
        HStack {
          Text("\(item.text)")
          Spacer()
          Button(action: { print("\(item.text) 1")}) {
            Text("Button 1")
          }
          Button(action: { print("\(item.text) 2")}) {
            Text("Button 2")
          }
        }
      }.buttonStyle(MyButtonStyle())
    }
  }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBradley MackeyView Question on Stackoverflow
Solution 1 - IosRamisView Answer on Stackoverflow
Solution 2 - IosMatteo PaciniView Answer on Stackoverflow
Solution 3 - IosJonathan BennettView Answer on Stackoverflow
Solution 4 - IosAntonView Answer on Stackoverflow