Multiple buttons in SwiftUI List rows

In this article we would like to share what we learned about buttons in SwiftUI List rows when developing one of our apps. We have a list in the form of a tree view. The parent row has a button to expand and collapse the children and each row also acts as a navigation link.

screenshot of an app on iPad that shows a list of items screenshot of an app on iPad that shows a list of items

Update: Note, that a similar user experience can be achieved using DisclosureGroup starting from iOS 14. But in this article we will continue exploring the example above.

# Two buttons in one row

It is possible to simply put two Button views in a single row.

List {
    HStack(spacing: 20) {
        Button(action: {
            self.isExpanded.toggle()
        }) {
            Expand(isExpanded: isExpanded)
        }
        Button(action: {
            self.isSelected.toggle()
        }) {
            Select(isSelected: isSelected)
        }
    }
}

But in this scenario tapping anywhere on the row will trigger both buttons at once.

# onTapGesture modifier and a button

We can replace the first Button with a custom view with onTapGesture modifier.

List {
    HStack(spacing: 20) {
        Expand(isExpanded: isExpanded)
            .onTapGesture {
                self.isExpanded.toggle()
            }
        Button(action: {
            self.isSelected.toggle()
        }) {
            Select(isSelected: isSelected)
        }
    }
}

In this case, tapping exactly on the Expand view will only toggle the expansion, but tapping anywhere else will trigger the Select view.

# Two views with onTapGesture modifier

To avoid transforming the entire row into a tappable button, we can only use custom views with onTapGesture modifiers and no buttons at all.

List {
    HStack(spacing: 20) {
        Expand(isExpanded: isExpanded)
            .onTapGesture {
                self.isExpanded.toggle()
            }
        Select(isSelected: isSelected)
            .onTapGesture {
                self.isSelected.toggle()
        }
    }
}

This way tapping exactly on the views will trigger their actions and tapping anywhere else on the row will do nothing.

# Summary

When using Button views and onTapGesture modifiers in list rows in SwiftUI we can achieve various results depending on our needs. We have to keep in mind that if we use at least one Button the whole row will become tappable and tapping anywhere on the row will trigger all of the buttons. Views with onTapGesture modifier do not have the same behavior and are only triggered when user taps exactly on their area.

You can find the full code for the examples above in our GitHub folder for this article.