One of the most frustrating aspects of working with SwiftUI is trying to debug unexpected behavior. I recently ran into a noodle-scratcher while adding search and interactivity features to views inside of a List
. Here, I will briefly show you a few unexpected behaviors you might run into when working with a List
view that has NavigationLinks
and provide you with a simple fix!
Adding a button to a cell that has a navigation link.
If you try and add a button to a cell that has a navigation link you will notice that the button is essentially ignored and anywhere you tap on the cell pushes the linked view onto the stack. To demonstrate, here is a snippet of code with a list of X-Men:
struct NavigableListWithButton: View {
private let xmen = ["Wolverine","Cyclops", "Storm", "Rogue", "Iceman", "Beast", "Professor X"]
@State var currentFavorite = ""
var body: some View {
NavigationView {
List {
ForEach(xmen, id:\.self) { teamMember in
NavigationLink(destination: Text(teamMember)) {
HStack {
Button(action: {currentFavorite = teamMember}, label: {
Image(systemName: (teamMember == currentFavorite ? "heart.fill" : "heart"))
.foregroundColor(.pink)
})
Text(teamMember)
}
}
}
}
.navigationBarTitle("The X-Men", displayMode: .automatic)
}
}
}
I have added a heart button to allow the user to select their favorite, but sadly things are not working as expected.
This behavior also partially manifests in views without navigation links. For example, if we add a view to the top of the list that has a sort button the entire view will become tappable and enact the function of the button.
The Solution
Every Button
in SwiftUI has a style that dictates its behavior and appearance. If you don’t set the style explicitly using the buttonStyle
modifier SwiftUI will pick an appropriate style based on the platform and context. Apple mentions this briefly in their documentation on DefaultButtonStyle, but doesn’t provide information on which styles are chosen when. In this case, leaving the behavior up to SwiftUI seems to be causing the problem. If you set the style of your buttons explicitly, as in:
Button(action: {
currentFavorite = teamMember
}, label: {
Image(systemName: (teamMember == currentFavorite ? "heart.fill" : "heart"))
.foregroundColor(.pink)
})
.buttonStyle(PlainButtonStyle())
things will work as you originally intended. As a side note, it doesn’t actually appear to matter what button style you choose, so long as you don’t let SwiftUI decide for you. I hope you have found this short post helpful. So which X-man is your favorite?