![](/img/trans.png)
[英]Using multiple UIGestureRecognizers simultaneously like UIRotationGestureRecognizer & UIPanGestureRecognizer in Swift 3
[英]Using SwiftUI with multiple UIGestureRecognizers?
我目前正在尝试编写一个应用程序,该应用程序使用类似于 Affinity 或 Procreate 等应用程序的导航,您可以使用单次触摸/拖动来绘制、修饰和交互,并使用两指手势在画布上导航。
我正在使用SwiftUI作为主要框架构建应用程序,并根据需要包含 UIKit 。
不幸的是,SwiftUI 还不允许像所描述的那样复杂的手势,但 UIKit 通常允许。 所以我恢复使用 UIKit 作为我的手势识别器,而不是依赖 SwiftUI 手势。 然而,这会导致问题,即只会调用最顶层的手势识别器。 我希望能够识别像此处演示的多个同时手势,但不幸的是,SwiftUI 似乎会导致 UIViewRepresentables 出现问题。
有人可以帮我找出解决方案吗?
重要提示:我使用两个单独的视图来执行此操作,因为从长远来看,它们将用于不同的视图。 但是,在示例中,出于演示目的,我将它们放在同一个视图中。
用法:
ZStack {
DragGestureView { point in
print("One Finger")
} dragEndedCallback: {
print("One Finger Ended")
}
TwoFingerNavigationView { point in
viewStore.send(.dragChanged(point))
print("Two Fingers")
} dragEndedCallback: {
viewStore.send(.dragEnded)
print("Two Fingers Ended")
} pinchedCallback: { value in
viewStore.send(.magnificationChanged(value))
} pinchEndedCallback: {
viewStore.send(.magnificationEnded)
}
content()
.position(viewStore.location)
.scaleEffect(viewStore.scale * viewStore.offsetScale)
}
拖动手势视图
public struct DragGestureView: UIViewRepresentable {
let delegate = GestureRecognizerDelegate()
var draggedCallback: ((CGPoint) -> Void)
var dragEndedCallback: (() -> Void)
public init(draggedCallback: @escaping ((CGPoint) -> Void), dragEndedCallback: @escaping (() -> Void)) {
self.draggedCallback = draggedCallback
self.dragEndedCallback = dragEndedCallback
}
public class Coordinator: NSObject {
var draggedCallback: ((CGPoint) -> Void)
var dragEndedCallback: (() -> Void)
public init(draggedCallback: @escaping ((CGPoint) -> Void),
dragEndedCallback: @escaping (() -> Void)) {
self.draggedCallback = draggedCallback
self.dragEndedCallback = dragEndedCallback
}
@objc func dragged(gesture: UIPanGestureRecognizer) {
if gesture.state == .ended {
self.dragEndedCallback()
} else {
self.draggedCallback(gesture.location(in: gesture.view))
}
}
}
class GestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
public func makeUIView(context: UIViewRepresentableContext<DragGestureView>) -> DragGestureView.UIViewType {
let view = UIView(frame: .zero)
let gesture = UIPanGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.dragged))
gesture.minimumNumberOfTouches = 1
gesture.maximumNumberOfTouches = 1
gesture.delegate = delegate
view.addGestureRecognizer(gesture)
return view
}
public func makeCoordinator() -> DragGestureView.Coordinator {
return Coordinator(draggedCallback: self.draggedCallback,
dragEndedCallback: self.dragEndedCallback)
}
public func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<DragGestureView>) {
}
}
双指导航视图
struct TwoFingerNavigationView: UIViewRepresentable {
let delegate = GestureRecognizerDelegate()
var draggedCallback: ((CGPoint) -> Void)
var dragEndedCallback: (() -> Void)
var pinchedCallback: ((CGFloat) -> Void)
var pinchEndedCallback: (() -> Void)
class Coordinator: NSObject {
var draggedCallback: ((CGPoint) -> Void)
var dragEndedCallback: (() -> Void)
var pinchedCallback: ((CGFloat) -> Void)
var pinchEndedCallback: (() -> Void)
var startingDistance: CGFloat? = nil
var isMagnifying = false
var startingMagnification: CGFloat? = nil
var newMagnification: CGFloat = 1.0
init(draggedCallback: @escaping ((CGPoint) -> Void),
dragEndedCallback: @escaping (() -> Void),
pinchedCallback: @escaping ((CGFloat) -> Void),
pinchEndedCallback: @escaping (() -> Void)) {
self.draggedCallback = draggedCallback
self.dragEndedCallback = dragEndedCallback
self.pinchedCallback = pinchedCallback
self.pinchEndedCallback = pinchEndedCallback
}
@objc func dragged(gesture: UIPanGestureRecognizer) {
if gesture.state == .ended {
self.dragEndedCallback()
self.pinchEndedCallback()
startingDistance = nil
isMagnifying = false
startingMagnification = nil
newMagnification = 1.0
} else {
self.draggedCallback(gesture.translation(in: gesture.view) / (newMagnification))
}
var touchLocations: [CGPoint] = []
for i in 0..<gesture.numberOfTouches{
touchLocations.append(gesture.location(ofTouch: i, in: gesture.view))
}
if touchLocations.count == 2 {
let distanceVector = (touchLocations[0] - touchLocations[1])
let distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y * distanceVector.y)
guard startingDistance != nil else { startingDistance = distance; return }
guard distance - startingDistance! > 30 || distance - startingDistance! < -30 || isMagnifying else { return }
isMagnifying = true;
if startingMagnification == nil {
startingMagnification = distance / 100
pinchedCallback(1)
} else {
let magnification = distance / 100
newMagnification = magnification / startingMagnification!
pinchedCallback(newMagnification)
}
}
}
}
class GestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
func makeUIView(context: UIViewRepresentableContext<TwoFingerNavigationView>) -> TwoFingerNavigationView.UIViewType {
let view = UIView(frame: .zero)
let gesture = UIPanGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.dragged))
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
gesture.delegate = delegate
view.addGestureRecognizer(gesture)
return view
}
func makeCoordinator() -> TwoFingerNavigationView.Coordinator {
return Coordinator(draggedCallback: self.draggedCallback,
dragEndedCallback: self.dragEndedCallback,
pinchedCallback: self.pinchedCallback,
pinchEndedCallback: self.pinchEndedCallback)
}
func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<TwoFingerNavigationView>) {
}
}
我没有在官方文档中找到任何关于我的发现的确认,但似乎 SwiftUI 手势的工作方式与 UIKit 不同。 一般来说 - 对于复杂的情况,您可以使用一种或另一种。
如果您使用任何类型的手势将 SwiftUI 视图添加到另一个视图,那么,正如您所提到的,只有顶级视图手势有效。 在 UIKit 中也是如此:1) 首先 hitTests 找到给定点的所有视图,然后 2) 系统收集这些视图的所有 UIGestureRecognizers,然后 3) 它尝试将触摸传递给手势识别器系统并检查哪些手势识别器会收到触摸,哪些会失败。 因此,在您的情况下,您在步骤 1 中收集的层次结构中没有底层视图DragGestureView
。 因此,只有来自 TwoFingerNavigationView 的手势识别器在工作。
您的解决方案是将两个手势识别器放入一个 UIView 中。 它们将同时出现在步骤 2) 中,并且遵循您的实施将同时工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.