SwiftUI on macOS: double tap on list item

I am writing a SwiftUI-based mac app (Big Sur beta 2), which shows a List of items. The list is configured to support selection. I also want to support double tapping on selected items.

When I attempt to add a double tap gesture to my list item, it breaks the built-in single tap selection gesture.

I have tried using onTapGesture(count: 2) { ... }, .gesture(TapGesture(count: 2)...), and .simultaneousGesture(TapGesture(count: 2)...), with no luck.

What is the correct way to add double-tap support to a List without breaking single-tap selection?
Post not yet marked as solved Up vote post of jeremycloud Down vote post of jeremycloud
5.2k views
Add a Comment

Replies

Also looking for this answer.
.gesture(
                                TapGesture(count:1)
                                    .onEnded({
                                        print("Tap Displayed (Store: \(index)"))
                                    })) //.gesture
                            .highPriorityGesture(TapGesture(count:2)
                            .onEnded({
                                print("Double Tap Displayed (Store: \(index))"
               })) //.gesture

Make the single tap the main gesture then add the double tap as highPriorityGesture this gains you exclusivity of the two gestures but "hesitates" slightly on single tap as OS waits to be sure it isn't double tap (index is my ForEach index)
@jeremycloud have you found a solution for this?
Same question, no luck. Filed FB9067349.

Elements in a List on macOS 11 that contain a TextField work as expected. Clicking on the field triggers item selection. Hovering a bit reveals the editable field.

However, List selection is obviated when adding a button or an element with a gesture, simultaneousGesture, or highPriorityGesture with or without masks or with a built-in or custom Primitive Button Style accepting only double tap gestures.

This results in poor UI for a macOS app where double tap in a list would be an expected behavior that differs from a single tap. Unfortunately, the only workaround seems to be adjust UI with a "go" marker to the side... or skip a SwiftUI implementation. Weirdly, adding a gesture to the list item background works... once, and then starts seizing up.

The problem still appears in macOS Monterey beta 6. You cannot handle double-click without loosing/breaking the built-in List functionality. I filed a feedback too: FB9595044.

The issue with the gesture handler in the listRowBackground() is that it strangely resizes the items when they are moved. I haven't found a workaround for that either... but at least this bug has been fixed in macOS Monterey.

Same problem for me, if anyone found a workaround I'd love to know about it :)

Same problem for Table on macOS

Use simultaneously gesture detection. Sample below :)

@State var selectedItem: URL?

List(items, selection: $selectedItem) { url in
                let selectGesture = TapGesture()
                    .onEnded {
                        selectedItem = url
                    }

                let openGesture = TapGesture(count: 2)
                    .onEnded {
                        print("double clicked")
                    }

                ItemRow(url)
                    .gesture(openGesture.simultaneously(with: selectGesture))
}

@den73: Nice solution and it seems to work, at least partly. Do you also experience the issue, that sometimes on the first few clicks the element is not selected correctly with the blue selection? For me it happens, that there is a grey selection.

EDIT

Your solution also seems to break the behaviour, that you can move the selection while click and hold, then move the mouse over the items.

Unbelievable, that there is still no solution from Apple.

  • @joachim_me I found solution. Use content shape on ItemRow: ItemRow(url).contentShape(Rectangle()).gesture(openGesture.simultaneously(with: selectGesture)) Also ItemRow should expand on full with: ItemRow HStack Icon Text Spacer

Add a Comment

@joachim_me No, I do have selection issue as you described. Also selection move when you click-hold-move over empty space of the element.

I made a modifier which seems to do the trick. Uses an NSView to do the actual handling: https://gist.github.com/joelekstrom/91dad79ebdba409556dce663d28e8297

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()
        }
    }
}
  • Thank you very much, it works flawlessly!

Add a Comment

I'm guessing there hasn't been an official fix for this yet? I ran into similar problems with Table View as well.

In macOS Ventura, a new modifier, contextMenu(forSelectionType:menu:primaryAction:), was introduced to allow for a primary action to executed in selection-based containers along with the standard context menu.

List(selection: $selectedItem) {
    ...
}
.contextMenu(forSelectionType: String.self, menu: { _ in }) {
    // double tap action
}

Single tap selection still works as well as the double tap action (specific to macOS).


There was a contextAction(forSelectionType:action:) modifier originally which was removed and merged with the context menu one. I'm not sure why but it does mean you have to pass in an empty closure to the menu parameter if you want the same functionality.

  • Thanks for the reply BabyJ. Sort of basic questions but would that mean that users < macOS Ventura would have the same bug / experience or is this a new instance of SwiftUI that fixes these issues? Bit new to apple development.

  • This new API is currently only available from macOS Ventura onwards. Previous versions will have to do without.

Add a Comment