Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI on macOS - handle single-click and double-click at the same time

Consider this view in SwiftUI:

struct MyView: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 200, height: 200)
            .onTapGesture {
                print("single clicked")
            }
    }
}

Now we're handling single-click. Say you wanna handle double-click too, but with a separate callback.

You have 2 options:

  • Add double click handler after single click - this doesn't work at all
  • Add double click handler before single click handler, this kinda works:
struct MyView: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 200, height: 200)
            .onTapGesture(count: 2) {
                print("double clicked")
            }
            .onTapGesture {
                print("single clicked")
            }
    }
}

Double-click handler is called properly, but single-click handler is called after a delay of about 250ms.

Any ideas how to resolve this?

like image 361
Vojto Avatar asked Jan 30 '20 18:01

Vojto


2 Answers

Here is possible approach (tested with Xcode 11.2 / macOS 10.15)

struct MyView: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 200, height: 200)
            .gesture(TapGesture(count: 2).onEnded {
                print("double clicked")
            })
            .simultaneousGesture(TapGesture().onEnded {
                print("single clicked")
            })
    }
}
like image 138
Asperi Avatar answered Oct 19 '22 14:10

Asperi


The gesture-based solutions don't work correctly when selection is enabled on the List, since the first tap will delay the actual selection. I have made a double click modifier which works on any view and seems to solve it the way I expect in all cases:

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.overlay {
            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

like image 32
Accatyyc Avatar answered Oct 19 '22 14:10

Accatyyc