Search code examples
swiftlistswiftuiswiftui-navigationlinkswiftui-navigationstack

Button inside NavigationLink that's inside a list item not working correctly


I have a button that I would like to link to another view upon clicking on it. I was told to put it inside a NavigationLink, use .borderless button style, but it activates outside of its area on the entire list row. I experimented with using navigation view or disabling the navigationlink among other suggestions but it doesn't fix the problem or just messes up my view so I must be doing something wrong.

Here is the list item view

struct NodeListItem: View {
 var body: some View {
   LazyVStack(alignment: .leading) {
    //has more information up here I don't think is relevant
     HStack {
        NavigationLink(destination: UserMessageList(user: node.user!)) {
            Button("DM") {
              //don't actually need it to do anything besides changing views
             }
           //.borderless does nothing I want it to have a border
            .buttonStyle(.borderedProminent) 
            }
        }
     }
  }

Here is my list view

struct NodeList: View {
 var body: some View {
   NavigationStack {
     List(nodes, id: \.self) { node in
        NodeListItem(node: node)
            .contextMenu {
                 Button {
                    node.user!.mute = !node.user!.mute
                    } label: {
                        Label(node.user!.mute ? "Show DM Alerts" : "Hide DM Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash")
                    }
                    
                    if bleManager.connectedPeripheral != nil {
                        Button (role: .destructive) {
                            deleteNodeId = node.num
                            isPresentingDeleteNodeAlert = true
                        } label: {
                            Label("Delete Node", systemImage: "trash")
                        }
                    }
              }
        }
       .listStyle(.plain)
   }
}

Any help/suggestions are appreciated!


Solution

  • You should not use a NavigationLink. You should do the navigation programmatically in the button's action closure.

    Assuming the type of nodes is [Node], you can add a @State representing the navigation path like this:

    @State private var path: [Node] = []
    let nodes = [...]
    
    var body: some View {
        NavigationStack(path: $path) {
            List(nodes, id: \.self) { node in
                NodeListItem(node: node) {
                    // programmatic navigation
                    path.append(node)
                }
                .contextMenu { ... }
            }
            .navigationDestination(for: Node.self) { node in
                UserMessageList(user: node.user!)
            }
        }
    }
    

    Note that the navigation destination of UserMessageList is declared on the List, instead of each individual NodeListItem.

    NodeListItem should be changed to take a closure for its button's action:

    struct NodeListItem: View {
        let node: Node
        let buttonAction: () -> Void
        
        var body: some View {
            LazyVStack(alignment: .leading) {
                // ...
                HStack {
                    // ...
                    Button("DM") {
                        buttonAction()
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
        }
    }
    

    Of course, you can also pass a @Binding of the navigation path, and do path.append in NodeListItem:

    struct NodeListItem: View {
        let node: Node
        @Binding var path: [Node]
        
        var body: some View {
            LazyVStack(alignment: .leading) {
                // ...
                HStack {
                    // ...
                    Button("DM") {
                        path.append(node)
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
        }
    }
    
    // ...
    
    NodeListItem(node: node, path: $path)
    

    Personally I think the former makes more sense.