Поиск местоположения щелчка в SwiftUI на MacOS
Я попытался адаптировать решение, которое я использовал в приложении iOS, к macOS, используя NSViewRepresentable вместо UIViewRepresentable.
Ниже мой "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>) {
}
}
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>) {
}
}
Лучшее решение — использовать комбинацию жестов 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)
}
}
Вы можете использовать его через удобный API
.onClickGesture(count:perform:)
как и любой другой жест SwiftUI.