7

I am trying to get a context menu to navigate to another view using the following code

var body: some View
{
    VStack
    {
        Text(self.event.name).font(.body)
        ...
        Spacer()
        NavigationLink(destination: EditView(event: self.event))
        {
            Image(systemName: "pencil")
        }
    }
    .navigationBarTitle(Text(appName))
    .contextMenu
    {
        NavigationLink(destination: EditView(event: self.event))
        {
            Image(systemName: "pencil")
        }
    }
}

The NavigationLink within the VStack works as expected and navigates to the edit view but I want to use a contextMenu. Although the context menu displays the image, when I tap on it it doesn't navigate to the edit view, instead it just cancels the context menu.

I am doing this within a watch app but don't think that should make a difference, is there anything special I have to do with context menu navigation?

4
  • 2
    I've had my many hours of pain this weekend trying to get navigation to work. I concluded that SwiftUI is far from finished and especially lacks & has weird bugs/side-effects with navigation. Commented Oct 7, 2019 at 14:52
  • Does it help when you embed the VStack in a NavigationView? Commented Oct 7, 2019 at 14:56
  • @meaning-matters - VStack makes no difference, I have tried numerous different ways of doing this without success, this was the cleanest to present. Thanks for suggestion though. Commented Oct 7, 2019 at 14:59
  • 1
    I can only wish you good luck! Commented Oct 7, 2019 at 15:01

4 Answers 4

5

In Xcode 11.4 it's now possible to do this with sensible NavigationLink buttons. Yay! 🎉

.contextMenu {
    NavigationLink(destination: VisitEditView(visit: visit)) {
        Text("Edit visit")
        Image(systemName: "square.and.pencil")
    }
    NavigationLink(destination: SegmentsEditView(timelineItem: visit)) {
        Text("Edit individual segments")
        Image(systemName: "ellipsis")
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Can you elaborate on this, please? I am not having success with this in Xcode 11.4.
The code I posted there is an exact copy paste from my app. So that code is working for me. What happens when you try it?
Sorry, I figured it out. I wasn't wrapping your NavigationLinks in a NavigationView, duh. Thank you!
New challenge, however. It is not working for me if the object displaying the contextMenu is itself wrapped in a NavigationLink. That is, NavigationView { NavigationLink(destination: EmptyView()) { Text("foo").contextMenu { NavigationLink(destination: EmptyView()) { Text("bar") } } } } works when I click "foo", but not when I click "bar" in the contextMenu.
No, same result, unfortunately, whether the destination is EmptyView(), a Text view, or a DestinationView(). The NavigationLink on the contextMenu item will not navigate to its destination if the object displaying the contextMenu is also wrapped in a NavigationLink.
|
4

I would use the isActive variant of NavigationLink that you can trigger by setting a state variable. Apple documents this here

This variant of NavigationLink is well fit for dynamic/programatic navigation.

Your .contextMenu sets the state variable to true and that activates the NavigationLink. Because you don't want the link to be visible, set the label view to EmptyView

Here's an example, not identical to your post but hopefully makes it clear.

struct ContentView: View {

    @State private var showEditView = false

    var body: some View {

        NavigationView {
            VStack {
                Text("Long Press Me")
                    .contextMenu {
                        Button(action: {
                            self.showEditView = true
                        }, label: {
                            HStack {
                                Text("Edit")
                                Image(systemName: "pencil")
                            }
                        })
                }
                NavigationLink(destination: Text("Edit Mode View Here"), isActive: $showEditView) {
                    EmptyView()
                }
            }
            .navigationBarTitle("Context Menu")
        }
    }
}

6 Comments

I implemented this and I can't get the NavigationLink to display the Text. I placed a Print statement in Button(action so know that is getting triggered but still no joy.
A bit more investigation shows the issue to be EmptyView. If I change it to NavigationLink(destination: EditView(event: self.event), isActive: $showEditView) {Text("Edit")} it works, but obviously the whole idea of a context menu is I don't want a button shown all the time.
Where do you have the NavigationLink in the view hierarchy? EmptyView() has worked in every example I've used but maybe its dependent on where it lives?
I am adding it into the VStack the same as your example. The other point to note is that NavigationView is not supported on WatchOS so VStack is my top level within the View.
Great. I didn’t realize that EmptyView doesn’t work on watchOS. Nice tip about .hidden()
|
1

This works on Xcode 11.6

struct ContentView: View {
    
    @State var isActiveFromContextMenu = false
    var body: some View {
        NavigationView{
            VStack{
                NavigationLink(destination : detailTwo(), isActive: $isActiveFromContextMenu ){
                    EmptyView()
                }
                
                List{
                    NavigationLink(destination: detail() ){
                        row(isActiveFromContextMenu: $isActiveFromContextMenu)
                    }
                    NavigationLink(destination: detail() ){
                        row(isActiveFromContextMenu: $isActiveFromContextMenu)
                    }
                    NavigationLink(destination: detail() ){
                        row(isActiveFromContextMenu: $isActiveFromContextMenu)
                    }
                }
            }       
        }   
    }    
}

struct detail: View {
    var body: some View{
        Text("Detail view")
    }
}

struct detailTwo: View {
    var body: some View{
        Text("DetailTwo view")
    }
}

struct row: View {
    
    @Binding var isActiveFromContextMenu : Bool
    var body: some View {
        
        HStack{
            
            Text("item")
            
        }.contextMenu{
            Button(action: {
                self.isActiveFromContextMenu = true
            })
            {
                Text("navigate to")
            }
        }
    }
}

Comments

1

I found success in masking the NavigationLink in the background and switching the context with a Button as the shortest yet simplest alternative.

struct ContentView: View {
    @State private var isShowing = false

    var body: some View {
        NavigationView {
            Text("Hello")
                .background(NavigationLink("", destination: Text("World!"), isActive: $isShowing))
                .contextMenu {
                    Button {
                        isShowing = true
                    } label: {
                        Label("Switch to New View", systemImage: "chevron.forward")
                    }
                }
        }
    }
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.