1

Consider the following view code:

Text("Something")
.contextMenu {
    // Some menu options
}

This works fine. What I would like to do: present the contextMenu through a view modifier indirection. Something like this:

Text("Something")
.modifier(myContextMenu) {
    // Some menu options
}

Why: I need to do some logic inside the modifier to conditionally present or not present the menu. I can’t work out the correct view modifier signature for it.

There is another contextMenu modifier available which claims that I can conditionally present the context menu for it. Upon trying this out, this does not help me, because as soon as I add contextMenu modifier to a NavigationLink on iOS, the tap gesture on it stops working. There is discussion in a response below.

How do I present a context menu using a view modifier?

Jaanus
  • 17,688
  • 15
  • 65
  • 110

3 Answers3

3

Here is a demo for optional context menu usage (tested with Xcode 11.2 / iOS 13.2)

enter image description here

struct TestConditionalContextMenu: View {
    @State private var hasContextMenu = false
    var body: some View {
        VStack {
            Button(hasContextMenu ? "Disable Menu" : "Enable Menu")
                { self.hasContextMenu.toggle() }
            Divider()
            Text("Hello, World!")
                .background(Color.yellow)
                .contextMenu(self.hasContextMenu ?
                    ContextMenu {
                            Button("Do something1") {}
                            Button("Do something2") {}
                    } : nil)
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Darn. I wish I could accept this answer. It looks correct and works, but my case is more complicated, as it turns out. The only reason why I am doing this in the first place, is to attach a contextMenu to a different item in iOS and OSX. If attaching to NavigationLink, it works on OSX but blocks taps in iOS. If attaching to an element inside NavigationLink, it works in iOS but doesn’t show up in OSX. I thought that conditional attaching is the answer, but nope - when I attach a nil contextmenu to NavigationLink, it still blocks the taps. – Jaanus Jan 27 '20 at 14:49
2

Something like this?

Text("Options")
        .contextMenu {
            if (1 == 0) { // some if statements here
                Button(action: {
                    //
                }) {
                    Text("Choose Country")
                    Image(systemName: "globe")
                }
            }
    }
Bartosz
  • 378
  • 3
  • 11
1

Here’s what I came up with. Not entirely satisfied, it could be more compact, but it works as expected.

struct ListView: View {    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: ItemView(item: "Something")) {
                    Text("Something").modifier(withiOSContextMenu())
                }.modifier(withOSXContextMenu())
            }
        }
    }
}

struct withOSXContextMenu: ViewModifier {
    func body(content: Content) -> some View {
        #if os(OSX)
        return content.contextMenu(ContextMenu {
            ContextMenuContent()
        })
        #else
        return content
        #endif
    }
}

struct withiOSContextMenu: ViewModifier {
    func body(content: Content) -> some View {
        #if os(iOS)
        return content.contextMenu(ContextMenu {
            ContextMenuContent()
        })
        #else
        return content
        #endif
    }
}

func ContextMenuContent() -> some View {
    Group {
        Button("Click me") {
            print("Button clicked")
        }
        Button("Another button") {
            print("Another button clicked")
        }
    }
}


Jaanus
  • 17,688
  • 15
  • 65
  • 110