我写了一个易于使用的类。
基本用法:
// define
var snapGesture: SnapGesture?
// add gesture
self.snapGesture = SnapGesture(view: self.testView!)
// remove gesture
self.snapGesture = nil
高级用法,对于视图接收手势为背景视图的场景:
// add gesture
self.snapGesture = SnapGesture(transformView: self.testView!, gestureView: self.view)
// remove gesture
self.snapGesture = nil
类:
import UIKit
/*
usage:
add gesture:
yourObjToStoreMe.snapGesture = SnapGesture(view: your_view)
remove gesture:
yourObjToStoreMe.snapGesture = nil
disable gesture:
yourObjToStoreMe.snapGesture.isGestureEnabled = false
advanced usage:
view to receive gesture(usually superview) is different from view to be transformed,
thus you can zoom the view even if it is too small to be touched.
yourObjToStoreMe.snapGesture = SnapGesture(transformView: your_view_to_transform, gestureView: your_view_to_recieve_gesture)
*/
class SnapGesture: NSObject, UIGestureRecognizerDelegate {
// MARK: - init and deinit
convenience init(view: UIView) {
self.init(transformView: view, gestureView: view)
}
init(transformView: UIView, gestureView: UIView) {
super.init()
self.addGestures(v: gestureView)
self.weakTransformView = transformView
}
deinit {
self.cleanGesture()
}
// MARK: - private method
private weak var weakGestureView: UIView?
private weak var weakTransformView: UIView?
private var panGesture: UIPanGestureRecognizer?
private var pinchGesture: UIPinchGestureRecognizer?
private var rotationGesture: UIRotationGestureRecognizer?
private func addGestures(v: UIView) {
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panProcess(_:)))
v.isUserInteractionEnabled = true
panGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(panGesture!)
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchProcess(_:)))
//view.isUserInteractionEnabled = true
pinchGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(pinchGesture!)
rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotationProcess(_:)))
rotationGesture?.delegate = self
v.addGestureRecognizer(rotationGesture!)
self.weakGestureView = v
}
private func cleanGesture() {
if let view = self.weakGestureView {
//for recognizer in view.gestureRecognizers ?? [] {
// view.removeGestureRecognizer(recognizer)
//}
if panGesture != nil {
view.removeGestureRecognizer(panGesture!)
panGesture = nil
}
if pinchGesture != nil {
view.removeGestureRecognizer(pinchGesture!)
pinchGesture = nil
}
if rotationGesture != nil {
view.removeGestureRecognizer(rotationGesture!)
rotationGesture = nil
}
}
self.weakGestureView = nil
self.weakTransformView = nil
}
// MARK: - API
private func setView(view:UIView?) {
self.setTransformView(view, gestgureView: view)
}
private func setTransformView(_ transformView: UIView?, gestgureView:UIView?) {
self.cleanGesture()
if let v = gestgureView {
self.addGestures(v: v)
}
self.weakTransformView = transformView
}
open func resetViewPosition() {
UIView.animate(withDuration: 0.4) {
self.weakTransformView?.transform = CGAffineTransform.identity
}
}
open var isGestureEnabled = true
// MARK: - gesture handle
// location will jump when finger number change
private var initPanFingerNumber:Int = 1
private var isPanFingerNumberChangedInThisSession = false
private var lastPanPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func panProcess(_ recognizer:UIPanGestureRecognizer) {
if isGestureEnabled {
//guard let view = recognizer.view else { return }
guard let view = self.weakTransformView else { return }
// init
if recognizer.state == .began {
lastPanPoint = recognizer.location(in: view)
initPanFingerNumber = recognizer.numberOfTouches
isPanFingerNumberChangedInThisSession = false
}
// judge valid
if recognizer.numberOfTouches != initPanFingerNumber {
isPanFingerNumberChangedInThisSession = true
}
if isPanFingerNumberChangedInThisSession {
return
}
// perform change
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPanPoint.x, y: point.y - lastPanPoint.y)
lastPanPoint = recognizer.location(in: view)
}
}
private var lastScale:CGFloat = 1.0
private var lastPinchPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func pinchProcess(_ recognizer:UIPinchGestureRecognizer) {
if isGestureEnabled {
guard let view = self.weakTransformView else { return }
// init
if recognizer.state == .began {
lastScale = 1.0;
lastPinchPoint = recognizer.location(in: view)
}
// judge valid
if recognizer.numberOfTouches < 2 {
lastPinchPoint = recognizer.location(in: view)
return
}
// Scale
let scale = 1.0 - (lastScale - recognizer.scale);
view.transform = view.transform.scaledBy(x: scale, y: scale)
lastScale = recognizer.scale;
// Translate
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPinchPoint.x, y: point.y - lastPinchPoint.y)
lastPinchPoint = recognizer.location(in: view)
}
}
@objc func rotationProcess(_ recognizer: UIRotationGestureRecognizer) {
if isGestureEnabled {
guard let view = self.weakTransformView else { return }
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
}
//MARK:- UIGestureRecognizerDelegate Methods
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
}
现在,您已经到了这一点,请继续。
如您所知,superView 中的手势可能会吃掉 subView 的事件,而在 snapchat 示例中,手势会吃掉工具栏的事件,如果我们触摸工具栏,我们需要防止 superview 的任何手势识别。
这个想法是在工具栏上添加一个pseduo自定义手势,因此任何手势都将被阻止到superview,并且这个pseduo手势除了将手势或事件传递给子视图或它自己的视图之外什么都不做。
在这里,我也写了一个类,方便使用。
用法:
toolbarView.addGestureRecognizer(SnapBlockGestureRecognizer)
实现:
import UIKit
class SnapBlockGestureRecognizer: UIGestureRecognizer {
init() {
//self.init(target: self, action: #selector(__dummyAction))
super.init(target: nil, action: nil)
self.addTarget(self, action: #selector(__dummyAction))
self.cancelsTouchesInView = false
}
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.cancelsTouchesInView = false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if self.state == .possible {
self.state = .began
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .recognized
}
override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
return self.isGestureRecognizerAllowed(gr:preventingGestureRecognizer)
}
override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
return !(self.isGestureRecognizerAllowed(gr: preventedGestureRecognizer))
}
override func shouldBeRequiredToFail(by otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return !(self.isGestureRecognizerAllowed(gr: otherGestureRecognizer))
}
override func shouldRequireFailure(of otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func isGestureRecognizerAllowed(gr: UIGestureRecognizer) -> Bool {
return gr.view!.isDescendant(of: self.view!)
}
@objc func __dummyAction() {
// do nothing
// print("dummyAction")
}
}