Reputation: 31
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
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
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
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