Search code examples
swiftuidouble-clicknavigationviewswiftui-navigationlink

SwiftUI NavigationLink double click on List MacOS


Can anyone think how to call an action when double clicking a NavigationLink in a List in MacOS? I've tried adding onTapGesture(count:2) but it does not have the desired effect and overrides the ability of the link to be selected reliably.

var body: some View {
    NavigationView {
        List {
            ForEach(items) { item in
                NavigationLink(destination: Item(itemDetail: item)) {
                    ItemRow(itemRow: item) //<-my row view
                }.buttonStyle(PlainButtonStyle())
                 .simultaneousGesture(TapGesture(count:2)
                 .onEnded {
                     print("double tap")
                })
            }
        }
    }
}

EDIT:

I've set up a tag/selection in the NavigationLink and can now double or single click the content of the row. The only trouble is, although the itemDetail view is shown, the "active" state with the accent does not appear on the link. Is there a way to either set the active state (highlighted state) or extend the NavigationLink functionality to accept double tap as well as a single?

@State var selection:String?
var body: some View {
    NavigationView {
        List {
            ForEach(items) { item in
                NavigationLink(destination: Item(itemDetail: item), tag: item.id, selection: self.$selection) {
                    ItemRow(itemRow: item) //<-my row view
                }.onTapGesture(count:2) { //<- Needed to be first!
                    print("doubletap")
                }.onTapGesture(count:1) {
                    self.selection = item.id
                }
            }
        }
    }
}

Solution

  • Here's another solution that seems to work the best for me. It's a modifier that adds an NSView which does the actual handling. Works in List even with selection:

    extension View {
        /// Adds a double click handler this view (macOS only)
        ///
        /// Example
        /// ```
        /// Text("Hello")
        ///     .onDoubleClick { print("Double click detected") }
        /// ```
        /// - Parameters:
        ///   - handler: Block invoked when a double click is detected
        func onDoubleClick(handler: @escaping () -> Void) -> some View {
            modifier(DoubleClickHandler(handler: handler))
        }
    }
    
    struct DoubleClickHandler: ViewModifier {
        let handler: () -> Void
        func body(content: Content) -> some View {
            content.background {
                DoubleClickListeningViewRepresentable(handler: handler)
            }
        }
    }
    
    struct DoubleClickListeningViewRepresentable: NSViewRepresentable {
        let handler: () -> Void
        func makeNSView(context: Context) -> DoubleClickListeningView {
            DoubleClickListeningView(handler: handler)
        }
        func updateNSView(_ nsView: DoubleClickListeningView, context: Context) {}
    }
    
    class DoubleClickListeningView: NSView {
        let handler: () -> Void
    
        init(handler: @escaping () -> Void) {
            self.handler = handler
            super.init(frame: .zero)
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func mouseDown(with event: NSEvent) {
            super.mouseDown(with: event)
            if event.clickCount == 2 {
                handler()
            }
        }
    }
    

    https://gist.github.com/joelekstrom/91dad79ebdba409556dce663d28e8297