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?

meaning-matters
  • 21,929
  • 10
  • 82
  • 142
CodeChimp
  • 4,745
  • 6
  • 45
  • 61

4 Answers4

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")
        }
    }
}
KB-YYZ
  • 712
  • 7
  • 7
  • 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. – CodeChimp Oct 07 '19 at 17:47
  • 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. – CodeChimp Oct 07 '19 at 18:26
  • 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? – KB-YYZ Oct 07 '19 at 18:30
  • 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. – CodeChimp Oct 07 '19 at 18:42
  • Referring to this answer https://stackoverflow.com/a/57321795/552539 changing to a text label and adding .hidden() got it working! Thanks for the pointer in the right direction. – CodeChimp Oct 07 '19 at 18:52
  • 1
    Great. I didn’t realize that EmptyView doesn’t work on watchOS. Nice tip about .hidden() – KB-YYZ Oct 07 '19 at 19:37
4

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")
    }
}
sobri
  • 1,626
  • 15
  • 28
  • 2
    Can you elaborate on this, please? I am not having success with this in Xcode 11.4. – protasm Apr 05 '20 at 15:26
  • 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? – sobri Apr 06 '20 at 09:26
  • 1
    Sorry, I figured it out. I wasn't wrapping your NavigationLinks in a NavigationView, duh. Thank you! – protasm Apr 07 '20 at 14:38
  • 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. – protasm Apr 07 '20 at 14:48
  • Is that your actual code? Because a destination of "EmptyView" isn't going to be much to see You'll probably need to provide a non empty destination view. – sobri Apr 08 '20 at 13:42
  • Yes, actual code. But same result -- failure to navigate -- when I replace EmptyView() with Text("test"). – protasm Apr 08 '20 at 19:48
  • @protasm The destination needs to be a SwiftUI View file. So you need to make a DestinationView.swift file, and use destination: DestinationView() – sobri Apr 09 '20 at 10:40
  • 4
    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. – protasm Apr 10 '20 at 15:20
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")
            }
        }
    }
}
David Buck
  • 3,752
  • 35
  • 31
  • 35
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")
                    }
                }
        }
    }
}
Klay
  • 45
  • 2
  • 6