SwiftUI NavigationLink 双击列表 MacOS

SwiftUI NavigationLink double click on List MacOS

谁能想到在 MacOS 中双击列表中的 NavigationLink 时如何调用操作?我试过添加 onTapGesture(count:2) 但它没有达到预期的效果,并且覆盖了 link 的可靠选择能力。

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

编辑:

我在 NavigationLink 中设置了 tag/selection,现在可以双击或单击该行的内容。唯一的麻烦是,虽然显示了 itemDetail 视图,但 link 上没有出现带有重音符号的“活动”状态。有没有办法设置活动状态(突出显示状态)或扩展 NavigationLink 功能以接受双击和单击?

@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
                }
            }
        }
    }
}

使用同步手势,如下所示(使用 Xcode 11.4 / macOS 10.15.5 测试)

NavigationLink(destination: Text("View One")) {
    Text("ONE")
}
.buttonStyle(PlainButtonStyle())        // << required !!
.simultaneousGesture(TapGesture(count: 2)
    .onEnded { print(">> double tap")})

.highPriorityGesture(...如果你需要双击有更高的优先级

寻找类似的解决方案,我尝试了@asperi 的回答,但在可点击区域方面遇到了与原始海报相同的问题。在尝试了许多变体之后,以下对我有用:

@State var selection: String?
...
NavigationLink(destination: HistoryListView(branch: string), tag: string, selection: self.$selection) {
  Text(string)
    .gesture(
      TapGesture(count:1)
        .onEnded({
          print("Tap Single")
          selection = string
        })
    )
    .highPriorityGesture(
      TapGesture(count:2)
        .onEnded({
          print("Tap Double")
        })
    )
}

我已经尝试了所有这些解决方案,但主要问题是使用 gesturesimultaneousGesture 覆盖列表视图上的默认单击手势,select列表。因此,我想到了一个简单的方法来保留默认的单击手势(select 行)并单独处理双击。

struct ContentView: View {
    @State private var monitor: Any? = nil
    @State private var hovering = false
    @State private var selection = Set<String>()
    
    let fruits = ["apple", "banana", "plum", "grape"]

    var body: some View {
        List(fruits, id: \.self, selection: $selection) { fruit in
            VStack {
                Text(fruit)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .clipShape(Rectangle()) // Allows the hitbox to be the entire word not the if you perfectly press the text  
            }
            .onHover {
                hovering = [=10=]
            }
        }
        .onAppear {
            monitor = NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) {
                if [=10=].clickCount == 2 && hovering { // Checks if mouse is actually hovering over the button or element
                    print("Double Tap!") // Run action
                }
                return [=10=]
            }
        }
        .onDisappear {
            if let monitor = monitor {
                NSEvent.removeMonitor(monitor)
            }
        }
    }
}

如果您只需要单击 select 和项目,则此方法有效,但仅当用户双击时才执行某些操作。如果你想处理单击和双击,当它是双击时,仍然存在单击 运行 的问题。一个潜在的解决方法是捕获单击动作并将其延迟几百毫秒,如果是双击动作则取消它

这是另一个似乎最适合我的解决方案。它是一个修饰符,添加了一个 NSView 来进行实际处理。即使有选择也适用于列表:

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