在 MacOS 上的 SwiftUI 中查找点击位置
Finding click location in SwiftUI on MacOS
我尝试使用 NSViewRepresentable 而不是 UIViewRepresentable 将我在 iOS 应用程序上使用的解决方案调整到 macOS。
下面是我的'Tappable View'。我的问题是当我尝试使用此视图时出现错误 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>) {
}
}
这是工作变体
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>) {
}
}
更好的解决方案是使用 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:)
使用它。
我尝试使用 NSViewRepresentable 而不是 UIViewRepresentable 将我在 iOS 应用程序上使用的解决方案调整到 macOS。
下面是我的'Tappable View'。我的问题是当我尝试使用此视图时出现错误 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>) {
}
}
这是工作变体
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>) {
}
}
更好的解决方案是使用 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:)
使用它。