swiftuiviewmodifier

How do I present a SwiftUI context menu conditionally?


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?


Solution

  • 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")
            }
        }
    }