nore982
nore982

Reputation: 31

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!

Upvotes: 2

Views: 596

Answers (3)

Robin kumar
Robin kumar

Reputation: 1

Using this method you can navigate form any button to any other screen and also impose conditions note the buttons are inside a List {} so, It can also manage that.

first create some variable to control i.e

struct ServicePackage: View {

@State private var selectedService: Service? = nil
@State private var isButtonClick = false
@State private var navigator = 0

then create a method for handling the navigation something like these according to your use

 private func destinationView(for service: Service? ,nav : Int) -> some View {
    guard let service = service else {
        return AnyView(Text("Invalid service"))
    }
    // Decide destination based on the service properties
    if(navigator == 1){
        return AnyView(PackageDescription())
    }
    else if navigator == 2 && !service.proPackages.isEmpty {
        return AnyView(PackageDetails())
    } else if navigator == 2 && service.proPackages.isEmpty {
        return AnyView(PackageCost())
    }else {
        return AnyView(EmptyView())
    }
}

then on the button click impose the navigation i.e

Button(action: {
    selectedService = service
    isButtonClick = true
    navigator = 1
}) {
    Text("See Details >")
        .padding(8)
        .foregroundColor(.blue)
}
.background(
    NavigationLink(
        destination: destinationView(for: selectedService, nav: navigator),
        isActive: $isButtonClick,
        label: { EmptyView() }
    )
    .hidden()
)
.buttonStyle(PlainButtonStyle())

Upvotes: 0

Benzy Neez
Benzy Neez

Reputation: 21730

A NavigationLink is itself a button and normally it can be styled using .buttonStyle in the same way as a regular Button. The reason why this doesn't work here, is because the item is an entry in a List.

However, if you style the label of the NavigationLink yourself, then you can avoid the nested button. Something like this:

NavigationLink {
    UserMessageList(user: node.user!)
} label: {
    Text("DM")
        .padding(.horizontal, 12)
        .padding(.vertical, 7)
        .foregroundStyle(.white)
        .background(
            RoundedRectangle(cornerRadius: 6)
                .fill(.link)
        )
}

The list row still has an arrow at the side and navigation happens when the row is tapped anywhere. If this is the problem you're trying to solve then not using a NavigationLink is probably the way to go -> see the answer by Sweeper for details of how to navigate programmatically instead.

Upvotes: 0

Sweeper
Sweeper

Reputation: 273540

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.

Upvotes: 1

Related Questions