在 MacOS 上的 SwiftUI 中查找点击位置

Posted

技术标签:

【中文标题】在 MacOS 上的 SwiftUI 中查找点击位置【英文标题】:Finding click location in SwiftUI on MacOS 【发布时间】:2020-11-28 05:46:24 【问题描述】:

我尝试使用 NSViewRepresentable 而不是 UIViewRepresentable 将我在 ios 应用程序上使用的解决方案调整到 macOS。

下面是我的“可点击视图”。我的问题是,当我尝试使用此视图时,我收到错误 Cannot find "TappableView" in scope

谢谢。

(使用 Xcode 版本 12.0 beta 4)

import Foundation
import SwiftUI

struct TappableView: NSViewRepresentable 
    
    var tappedCallback: ((CGPoint, Int) -> Void)
        
    func makeNSView(context: NSViewRepresentableContext<TappableView>) -> NSView 
        let v = UIView(frame: .zero)
        let gesture = NSClickGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tapped))
        gesture.numberOfTapsRequired = 1
        let gesture2 = NSClickGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.doubleTapped))
        gesture2.numberOfTapsRequired = 2
        gesture.require(toFail: gesture2)
        v.addGestureRecognizer(gesture)
        v.addGestureRecognizer(gesture2)
        return v
    
    
    class Coordinator: NSObject 
        var tappedCallback: ((CGPoint, Int) -> Void)
        init(tappedCallback: @escaping ((CGPoint, Int) -> Void)) 
            self.tappedCallback = tappedCallback
        
        @objc func tapped(gesture:NSClickGestureRecognizer) 
            let point = gesture.location(in: gesture.view)
            self.tappedCallback(point, 1)
        
        @objc func doubleTapped(gesture:NSClickGestureRecognizer) 
            let point = gesture.location(in: gesture.view)
            self.tappedCallback(point, 2)
        
    
    
    func makeCoordinator() -> TappableView.Coordinator 
        return Coordinator(tappedCallback:self.tappedCallback)
    
    
    func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<TappableView>) 
    
        


【问题讨论】:

【参考方案1】:

更好的解决方案是使用 SwiftUI 手势组合而不是 NSViewRepresentable 解决方案,因为它可以更好地与 SwiftUI API 集成。提出的其他解决方案的问题是,如果您想编写复杂的手势,则不能使用.highPriorityGesture() 或任何其他Gesture API。

这是一个有效的实现:

struct ClickGesture: Gesture 
    let count: Int
    let coordinateSpace: CoordinateSpace
    
    typealias Value = SimultaneousGesture<TapGesture, DragGesture>.Value
    
    init(count: Int = 1, coordinateSpace: CoordinateSpace = .local) 
        precondition(count > 0, "Count must be greater than or equal to 1.")
        self.count = count
        self.coordinateSpace = coordinateSpace
    
    
    var body: SimultaneousGesture<TapGesture, DragGesture> 
        SimultaneousGesture(
            TapGesture(count: count),
            DragGesture(minimumDistance: 0, coordinateSpace: coordinateSpace)
        )
    
    
    func onEnded(perform action: @escaping (CGPoint) -> Void) -> some Gesture 
        ClickGesture(count: count, coordinateSpace: coordinateSpace)
            .onEnded  (value: Value) -> Void in
                guard value.first != nil else  return 
                guard let location = value.second?.startLocation else  return 
                guard let endLocation = value.second?.location else  return 
                guard ((location.x-1)...(location.x+1)).contains(endLocation.x),
                      ((location.y-1)...(location.y+1)).contains(endLocation.y) else 
                    return
                
                
                action(location)
            
    


extension View 
    func onClickGesture(
        count: Int,
        coordinateSpace: CoordinateSpace = .local,
        perform action: @escaping (CGPoint) -> Void
    ) -> some View 
        gesture(ClickGesture(count: count, coordinateSpace: coordinateSpace)
            .onEnded(perform: action)
        )
    
    
    func onClickGesture(
        count: Int,
        perform action: @escaping (CGPoint) -> Void
    ) -> some View 
        onClickGesture(count: count, coordinateSpace: .local, perform: action)
    
    
    func onClickGesture(
        perform action: @escaping (CGPoint) -> Void
    ) -> some View 
        onClickGesture(count: 1, coordinateSpace: .local, perform: action)
    

您可以像任何其他 SwiftUI 手势一样通过便捷的 API .onClickGesture(count:perform:) 使用它。

【讨论】:

【参考方案2】:

这是工作变体

struct TappableView: NSViewRepresentable 

    var tappedCallback: ((CGPoint, Int) -> Void)

    func makeNSView(context: NSViewRepresentableContext<TappableView>) -> NSView 
        let v = NSView(frame: .zero)
        context.coordinator.configure(view: v)
        return v
    

    class Coordinator: NSObject, NSGestureRecognizerDelegate 
        var tappedCallback: ((CGPoint, Int) -> Void)
        private var gesture: NSClickGestureRecognizer!
        private var gesture2: NSClickGestureRecognizer!

        init(tappedCallback: @escaping ((CGPoint, Int) -> Void)) 
            self.tappedCallback = tappedCallback
        
        func configure(view: NSView) 
            gesture = NSClickGestureRecognizer(target: self, action: #selector(Coordinator.tapped))
            gesture.delegate = self
            gesture.numberOfClicksRequired = 1
            gesture2 = NSClickGestureRecognizer(target: self, action: #selector(Coordinator.doubleTapped))
            gesture2.delegate = self
            gesture2.numberOfClicksRequired = 2
            view.addGestureRecognizer(gesture)
            view.addGestureRecognizer(gesture2)
        
        @objc func tapped(gesture:NSClickGestureRecognizer) 
            let point = gesture.location(in: gesture.view)
            self.tappedCallback(point, 1)
        
        @objc func doubleTapped(gesture:NSClickGestureRecognizer) 
            let point = gesture.location(in: gesture.view)
            self.tappedCallback(point, 2)
        

        func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool 
            return gestureRecognizer === gesture && otherGestureRecognizer === gesture2
        
    

    func makeCoordinator() -> TappableView.Coordinator 
        return Coordinator(tappedCallback:self.tappedCallback)
    

    func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<TappableView>) 
    


【讨论】:

以上是关于在 MacOS 上的 SwiftUI 中查找点击位置的主要内容,如果未能解决你的问题,请参考以下文章

在 macOS SwiftUI 中模拟按钮点击?

在 macOS 上的 SwiftUI 列表视图中选择和删除核心数据实体

macOS 上的 SwiftUI:如何为 onDelete 启用 UI(从列表中删除)

SwiftUI:更改 macOS 上的默认命令菜单

让我们在 MacOS AppKit 上的 SwiftUI 中使用所有可用宽度的按钮

SwiftUI - TextField 在 macOS 上的 List 中被禁用(不可编辑)