iOS中手势(UIGestureRecognizer)的玩法

您所在的位置:网站首页 ios自定义手势怎么循环 iOS中手势(UIGestureRecognizer)的玩法

iOS中手势(UIGestureRecognizer)的玩法

2023-08-14 10:43| 来源: 网络整理| 查看: 265

UIGestureRecognizer class UIGestureRecognizer : NSObject

UIGestureRecognizer是具体手势识别器的基类,UIGestureRecognizer包括了以下具体的手势识别器

UITapRecognizer UIPinchRecognizer UIRotationRecognizer UISwipeRecognizer UIPanRecognizer UIScreenPanGestureRecognizer UILongGestureRecognizer

UIGestureRecognizer类中定义了一些通用的行为来配置具体的手势识别器。它能与代理对象(delegate)进行交互(该对象遵循代理协议UIGestureRecognizerDelegate),并进行一些自定义的行为

创建手势识别器 // Valid action method signatures: // -(void)handleGesture; // -(void)handleGesture:(UIGestureRecognizer*)gestureRecognizer; public init(target: Any?, action: Selector?) // designated initializer

init方法是指定构造器,用于创建一个手势对象并添加对应的触发事件。target对象是action实现的容器,action即响应事件

管理手势交互 weak open var delegate: UIGestureRecognizerDelegate? // the gesture recognizer's delegate

UIGestureRecognizerDelegate协议中有一系列手势相关的方法

public protocol UIGestureRecognizerDelegate : NSObjectProtocol { @available(iOS 3.2, *) optional func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool @available(iOS 3.2, *) optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool @available(iOS 7.0, *) optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool @available(iOS 7.0, *) optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool @available(iOS 3.2, *) optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool @available(iOS 9.0, *) optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool }

1、gestureRecognizerShouldBegin

开始进行手势识别时调用的方法,返回NO则结束识别,不再触发手势,用处:可以在控件指定的位置使用手势识别

2、shouldRecognizeSimultaneouslyWith

是否支持多手势触发,返回YES,则可以多个手势一起触发方法,返回NO则为互斥

是否允许多个手势识别器共同识别,一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO; 如果为YES,响应者链上层对象触发手势识别后,如果下层对象也添加了手势并成功识别也会继续执行,否则上层对象识别后则不再继续传播

3、shouldRequireFailureOf

这个方法返回YES,第一个手势和第二个互斥时,第一个会失效

4、shouldBeRequiredToFailBy

这个方法返回YES,第一个和第二个互斥时,第二个会失效

5、 shouldReceive touch

手指触摸屏幕后回调的方法,返回NO则不再进行手势识别,方法触发等

此方法在window对象有触摸事件发生时,调用gesture recognizer的touchesBegan:withEvent:方法之前调用,如果返回NO,则gesture recognizer不会看到此触摸事件。(默认情况下为YES)

6、shouldReceive press

添加和去除Targets和Actions open func addTarget(_ target: Any, action: Selector) // 给一个手势对象添加监听事件,可以指定多个target-action open func removeTarget(_ target: Any?, action: Selector?) // 移除一个手势的监听事件 获取手势的Touches和Location open func location(in view: UIView?) -> CGPoint open var numberOfTouches: Int { get } open func location(ofTouch touchIndex: Int, in view: UIView?) -> CGPoint

location(in view: UIView?)

获取当前触摸在指定视图上的点

numberOfTouches

获取触摸手指数

location(ofTouch touchIndex: Int, in view: UIView?)

多指触摸的触摸点相对于指定视图的位置

获取识别器的状态和视图(Getting the Recognizer’s State and View) open var state: UIGestureRecognizer.State { get } // the current state of the gesture recognizer open var isEnabled: Bool // 手势识别是否可用,默认是YES,设置为NO,将不能接受触摸事件 open var view: UIView? { get } // 当前获取手势触摸的View视图,该视图是 addGestureRecognizer:方法添加的

state是当前手势状态,拥有以下值

public enum State : Int { case possible // 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态 case began // 手势已经开始,此时已经被识别,但是这个过程中可能发生变化,手势操作尚未完成 case changed // 手势状态发生改变 case ended // 手势识别操作完成(此时已经松开手指) case cancelled // 手势被取消,恢复到默认状态 case failed // 手势识别失败,恢复到默认状态 public static var recognized: UIGestureRecognizer.State { get } } 取消和延迟触摸(Canceling and Delaying Touches) open var cancelsTouchesInView: Bool // default is YES. open var delaysTouchesBegan: Bool // default is NO. open var delaysTouchesEnded: Bool // default is YES

cancelsTouchesInView

表示当手势被识别的时候是否触摸事件应该被传递给视图(可理解为是否取消触摸控件的响应)。

该属性值默认为YES,这种情况下当手势识别器识别到触摸之后,会发送touchesCancelled给触摸到的控件以取消控件view对touch的响应,这个时候只有手势识别器响应touch,当设置成NO时,手势识别器识别到触摸之后不会发送touchesCancelled给控件,这个时候手势识别器和控件view均响应touch。

注意:手势识别和触摸事件是同时存在的,只是因为touchesCancelled导致触摸事件失效

看个简单例子来理解

func testCancelsTouchesInView() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) panGesture.cancelsTouchesInView = false self.view.addGestureRecognizer(panGesture) } @objc func handleGesture(_ gesture: UIPanGestureRecognizer) { print("handle gesture") } override func touchesMoved(_ touches: Set, with event: UIEvent?) { print("moved") }

上面把cancelTouchesInView设置为false时,在屏幕上滑动,会触发滑动手势和触摸两种方式,打印如下:

moved handle gesture moved handle gesture handle gesture moved handle gesture moved handle gesture moved handle gesture ....

把panGesture.cancelsTouchesInView = false注释掉。当手势触发时,将取消触摸消息的触发即默认情况下,打印如下

handle gesture handle gesture handle gesture handle gesture handle gesture handle gesture handle gesture ...

delaysTouchesBegan

上面的例子,我们知道,在一个手势触发之前,是会一并发消息给事件传递链的,delaysTouchesBgan属性用于控制这个消息的传递时机,是否延迟发送触摸事件给触摸到的控件

默认是NO,这种情况下当发生一个触摸时,手势识别器先捕捉到到触摸,然后发给触摸到的控件,两者各自做出响应。如果设置为YES,手势识别器在识别的过程中(注意是识别过程),不会将触摸发给触摸到的控件,即控件不会有任何触摸事件。只有在识别失败之后才会将触摸事件发给触摸到的控件,这种情况下控件view的响应会延迟约0.15ms

delaysTouchesEnded

这个属性设置手势识别结束后,是立刻发送touchesEnded消息到事件传递链或者延迟一个很短的时间后,如果没有接收到新的手势识别任务,再发送

指定手势器之间的依赖 open func require(toFail otherGestureRecognizer: UIGestureRecognizer)

指定一个手势需要另一个手势执行失败才会执行,同时触发多个手势使用其中一个手势的解决办法

有时手势是相关联的,如单机和双击,点击和长按,点下去瞬间可能只会识别到单击无法识别其他,该方法可以指定某一个 手势,即便自己已经满足条件了,也不会立刻触发,会等到该指定的手势确定失败之后才触发

识别不同的手势 @available(iOS 9.0, *) open var allowedTouchTypes: [NSNumber] // Array of UITouchTypes as NSNumbers. @available(iOS 9.0, *) open var allowedPressTypes: [NSNumber] // Array of UIPressTypes as NSNumbers. @available(iOS 9.2, *) open var requiresExclusiveTouchType: Bool // defaults to YES 调试手势识别器 @available(iOS 11.0, *) open var name: String? // name for debugging to appear in logging 状态机(state machine)

手势识别器由状态机来驱动,UIKit通过状态机来确保合理的处理事件。状态机确定了一些重要的行为

是否连续手势识别器允许进行UIGestureRecognizer.State.began 状态 是否离散手势识别器被允许进入UIGestureRecognizer.State.ended 状态 什么时候触发对应的响应事件

当实现自定义手势识别器的时候,必须在合适的事件更新手势的状态。手势状态经常开始于UIGestureRecognizer.State.possible状态,表示准备开始处理事件。对于离散和连续的手势识别器都有着不同的状态路径,一直到 UIGestureRecognizer.State.ended, UIGestureRecognizer.State.failed, or UIGestureRecognizer.State.cancelled。手势识别器最终的状态,将是上面的几种状态之一,后面UIKit将重置手势状态

离散手势识别器状态的过渡

对于离散手势识别器,你可以过渡的状态有两种 UIGestureRecognizer.State.ended 或者 UIGestureRecognizer.State.failed。如下图:

屏幕快照 2019-04-17 下午2.27.27.png

由上图可知,当即将到来的事件成功匹配手势的时候,手势的状态将会过渡到 UIGestureRecognizer.State.ended,并且调用关联的响应事件。当事件不能匹配对应的手势,状态将变为UIGestureRecognizer.State.failed,并不会触发相关的事件

连续手势识别器状态过渡

下图展示了连续手势识别器状态之间的过渡情况,主要分为3个阶段:

1、初始事件将手势状态移动到UIGestureRecognizer.State.began或者UIGestureRecognizer.State.failed 2、进一步的事件将手势状态变为UIGestureRecognizer.State.changed或者UIGestureRecognizer.State.cancelled 3、最终事件手势状态将变为UIGestureRecognizer.State.ended

屏幕快照 2019-04-17 下午2.32.04.png 子类手势识别器

在六种手势识别中,只有一种手势是离散型手势,他就是UITapGestureRecognizer。

离散型手势的特点就是:一旦识别就无法取消,而且只会调用一次手势操作事件(初始化手势时指定的回调方法)。

​换句话说其他五种手势是连续型手势,而连续型手势的特点就是:会多次调用手势操作事件,而且在连续手势识别后可以取消手势。下图可以看出两者调用操作事件的次数是不同的:

251202009061611.png UITapGestureRecognizer

UITapGestureRecognizer是UIGestureRecognizer子类,如类名所表达的意思一样,点击手势识别器,专门处理点击事件,该类很简单只有两个属性

@available(iOS 3.2, *) open class UITapGestureRecognizer : UIGestureRecognizer { open var numberOfTapsRequired: Int // Default is 1. The number of taps required to match open var numberOfTouchesRequired: Int // Default is 1. The number of fingers required to match } numberOfTapsRequired

识别到手势的最少的轻触次数(默认为1)

numberOfTouchesRequired

识别到手势的最少的手指的个数(默认为1)

简单使用

创建一个UIViewController,并添加一个测试视图(testView),实现如下

class GestureViewController: UIViewController { lazy var testView: UIView = { let view = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200)) view.backgroundColor = UIColor.red return view }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white view.addSubview(testView) // add code here... } }

为testView添加点击手势并实现响应事件,当点击testView的时候修改背景颜色

func addTapGesture() { // 创建点击手势对象 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))) // 设置点击次数 tapGesture.numberOfTapsRequired = 1; // 设置触摸手指个数 tapGesture.numberOfTouchesRequired = 1; // 为视图添加点击手势 testView.addGestureRecognizer(tapGesture) } @objc func handleTapGesture(_ gesture: UITapGestureRecognizer) { testView.backgroundColor = UIColor.yellow }

界面效果

tap.gif UIPinchGestureRecognizer

UIPinchGestureRecognizer捏合手势,缩放用

@available(iOS 3.2, *) open class UIPinchGestureRecognizer : UIGestureRecognizer { open var scale: CGFloat // scale relative to the touch points in screen coordinates open var velocity: CGFloat { get } // velocity of the pinch in scale/second }

scale 设置缩放比例

velocity 获取捏合速度,单位:缩放比/秒

简单使用

func addPinchGesture() { let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:))) testView.addGestureRecognizer(pinchGesture) } @objc func handlePinchGesture(_ gesture: UIPinchGestureRecognizer) { gesture.view?.transform = CGAffineTransform(scaleX: gesture.scale, y: gesture.scale) }

界面效果

pinch.gif UIRotationGestureRecognizer

UIRotationRecognizer旋转手势

@available(iOS 3.2, *) open class UIRotationGestureRecognizer : UIGestureRecognizer { open var rotation: CGFloat // rotation in radians open var velocity: CGFloat { get } // velocity of the pinch in radians/second }

rotation 旋转的角度

velocity 旋转速度,单位:度/秒

简单使用

func addRotationGesture() { let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotationGesture(_:))) testView.addGestureRecognizer(rotationGesture) } @objc func handleRotationGesture(_ gesture: UIRotationGestureRecognizer) { switch gesture.state { case .began: print("began") case .changed: print("changed") case .ended: print("ended") default: print("default") } print("rotation: \(gesture.rotation)") guard let recognizedView = gesture.view else { return } // 设置transform进行旋转 recognizedView.transform = CGAffineTransform(rotationAngle: gesture.rotation) // 方法2 实现旋转 // recognizedView.transform = recognizedView.transform.rotated(by: gesture.rotation) // gesture.rotation = 0 // 重置旋转 }

界面效果

rotation.gif UISwipeGestureRecognizer

UISwipeRecognizer轻扫手势

@available(iOS 3.2, *) open class UISwipeGestureRecognizer : UIGestureRecognizer { open var numberOfTouchesRequired: Int // default is 1. the number of fingers that must swipe open var direction: UISwipeGestureRecognizer.Direction } numberOfTouchesRequired

最少触摸手指个数,默认为1

direction

设置轻扫手势支持的方向,默认为向右滑,其他值如下结构体

public struct Direction : OptionSet { public init(rawValue: UInt) public static var right: UISwipeGestureRecognizer.Direction { get } // 向右滑 public static var left: UISwipeGestureRecognizer.Direction { get } // 向左滑 public static var up: UISwipeGestureRecognizer.Direction { get } // 向上滑 public static var down: UISwipeGestureRecognizer.Direction { get } // 向下滑 }

简单使用

同样是上面的例子,我们为testView添加扫动手势

func addSwipeGesture() { let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture(_:))) // 向左滑动 leftSwipe.direction = .left testView.addGestureRecognizer(leftSwipe) let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture(_:))) // 向右滑动 rightSwipe.direction = .right testView.addGestureRecognizer(rightSwipe) } @objc func handleSwipeGesture(_ gesture: UISwipeGestureRecognizer) { let direction = gesture.direction if direction == .left { print("left") } else if direction == .right { print("right") } } UIPanGestureRecognizer

UIPanGestureRecognizer

@available(iOS 3.2, *) open class UIPanGestureRecognizer : UIGestureRecognizer { open var minimumNumberOfTouches: Int // default is 1. the minimum number of touches required to match open var maximumNumberOfTouches: Int // default is UINT_MAX. the maximum number of touches that can be down open func translation(in view: UIView?) -> CGPoint // translation in the coordinate system of the specified view open func setTranslation(_ translation: CGPoint, in view: UIView?) open func velocity(in view: UIView?) -> CGPoint // velocity of the pan in points/second in the coordinate system of the specified view } minimumNumberOfTouches

设置触发拖拽最少手指数,默认为1

maximumNumberOfTouches

设置触发拖拽最多手指数,默认为 UINT_MAX 无限大

translation(in view: UIView?)

获取当前拖拽位置

setTranslation(_ translation: CGPoint, in view: UIView?)

设置当前拖拽位置

velocity(in view: UIView?)

拖拽速度,单位:像素/秒

简单使用

func addPanGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) testView.addGestureRecognizer(panGesture) } @objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) { // 获取手指拖拽的时候的值 let translation = gesture.translation(in: gesture.view) // 让当前控件平移 gesture.view?.transform = CGAffineTransform(translationX: translation.x, y: translation.y) }

界面效果

pan.gif UILongGestureRecognizer

UILongGestureRecognizer长按手势

@available(iOS 3.2, *) open class UILongPressGestureRecognizer : UIGestureRecognizer { open var numberOfTapsRequired: Int // Default is 0. The number of full taps required before the press for gesture to be recognized open var numberOfTouchesRequired: Int // Default is 1. Number of fingers that must be held down for the gesture to be recognized open var minimumPressDuration: TimeInterval // Default is 0.5. Time in seconds the fingers must be held down for the gesture to be recognized open var allowableMovement: CGFloat // Default is 10. Maximum movement in pixels allowed before the gesture fails. Once recognized (after minimumPressDuration) there is no limit on finger movement for the remainder of the touch tracking } numberOfTapsRequired

能识别到手势的最少的轻触次数(默认为1)

numberOfTouchesRequired

能识别到手势的最少的手指的个数(默认为1)

minimumPressDuration

能识别到长按手势的最短的长按时间,单位:秒,默认为0.5

allowableMovement

设置长按时允许移动的最大距离,单位:像素,默认为10像素

简单使用

func addLongPressGesture() { let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:))) // 长按手势的最小的长按时间2秒,,达到2秒触发action longPressGesture.minimumPressDuration = 2 testView.addGestureRecognizer(longPressGesture) } @objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) { switch gesture.state { case .began: print("began: \(Date())") case .changed: print("changed") case .ended: print("ended: \(Date())") default: print("default") } } UIScreenEdgePanGestureRecognizer

UIScreenEdgePanGestureRecognizer看起来像pan手势,它是检测屏幕边缘的pan手势的。系统在某些controller转场的时候会使用这个手势。你也可以使用这个手势做其他的事情。创建UIScreenEdgePanGestureRecognizer手势之后需要设置edges属性,确定滑动的方向

@available(iOS 7.0, *) open class UIScreenEdgePanGestureRecognizer : UIPanGestureRecognizer { open var edges: UIRectEdge //< The edges on which this gesture recognizes, relative to the current interface orientation }

简单使用

func addScreenEdgePanGesture() { testView.frame = view.frame let edgePanGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleScreenEdgePanGesture(_:))) // 屏幕右侧边缘响应滑动 edgePanGesture.edges = .right view.addGestureRecognizer(edgePanGesture) } @objc func handleScreenEdgePanGesture(_ gesture: UIScreenEdgePanGestureRecognizer) { let translation = gesture.translation(in: view) switch gesture.state { case .began, .changed: testView.center = CGPoint(x: view.center.x + translation.x, y: testView.center.y) default: testView.center = CGPoint(x: view.center.x, y: testView.center.y) } }

实现效果

screen.gif 自定义手势(Custom Gestures)

当UIKit所提供的手势识别器不能满足我们的需求时候,我们可以自定义手势。自定义手势可以分为两类,离散和连续。为了实现自定义效果,需要子类化UIGestureRecognizer,然后重写 UIGestureRecognizerSubclass.h中的方法

实现离散手势识别器

如果手势涉及了具体的事件模式,考虑实现离散手势识别器。实现如下图的手势,自定义一个打勾(确认)手势

aa0c0736-0d22-4521-ad21-938181d1e784.png

定义手势成功的条件

在我们实现手势代码之前,应该先考虑好什么情况下手势识别成功。对于打勾手势:

仅仅是手指首次触摸屏幕被记录,其余忽略 手指触摸是由左到右 触摸开始是向下,然后改变方向向上移动 向上移动到最后位置应该高于最初的位置

功能实现

enum CheckmarkPhases { case notStarted case initialPoint case downStroke case upStroke } // 自定义打勾手势识别器 class CheckmarkGestureRecognizer: UIGestureRecognizer { // 填充阶段 var storkPhase: CheckmarkPhases = .notStarted // 初始位置(点) var initialTouchPoint: CGPoint = CGPoint.zero var trackedTouch: UITouch? = nil // 在touchesBegan方法中创建初始条件用于手势识别 override func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) // 如果初始的触摸事件不为1,那么设置状态为failed if touches.count != 1 { self.state = .failed } // Capture the first touch and store some information about it. if self.trackedTouch == nil { self.trackedTouch = touches.first self.storkPhase = .initialPoint self.initialTouchPoint = (self.trackedTouch?.location(in: self.view))! } else { // Ignore all but the first touch. for touch in touches { if touch != self.trackedTouch { self.ignore(touch, for: event) } } } } // 当触摸发生改变时,UIKit将调用该方法 override func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) let newTouch = touches.first // There should be only the first touch. 确保是第一个触摸对象 guard newTouch == self.trackedTouch else { self.state = .failed return } let newPoint = (newTouch?.location(in: self.view))! let previousPoint = (newTouch?.previousLocation(in: self.view))! // 分阶段处理各种情况,根据移动点的位置进行比较 if self.storkPhase == .initialPoint { // Make sure the initial movement is down and to the right. // 确保初始移动是向右下方 if newPoint.x >= initialTouchPoint.x && newPoint.y >= initialTouchPoint.y { self.storkPhase = .downStroke } else { self.state = .failed } } else if self.storkPhase == .downStroke { // 向下移动 // Always keep moving left to right. 由左到右 if newPoint.x >= previousPoint.x { // If the y direction changes, the gesture is moving up again. // Otherwise, the down stroke continues. if newPoint.y < previousPoint.y { // 开始向上移动 self.storkPhase = .upStroke } } else { // If the new x value is to the left, the gesture fails. self.state = .failed } } else if self.storkPhase == .upStroke { // If the new x value is to the left, or the new y value // changed directions again, the gesture fails.] if newPoint.x < previousPoint.x || newPoint.y > previousPoint.y { self.state = .failed } } } // 滑动(移动)结束,调用该方法,如果手势到目前为止还没有失败,该方法将做最后两点判断 // 1 移动手势是否向上并且最终位置(点)小于初始位置(点),如果两个条件都满足,方法设置状态为可识别状态 override func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) let newTouch = touches.first let newPoint = (newTouch?.location(in: self.view))! // There should be only the first touch. guard newTouch == self.trackedTouch else { self.state = .failed return } if self.state == .possible && self.storkPhase == .upStroke && newPoint.y < initialTouchPoint.y { self.state = .recognized } else { self.state = .failed } } // 取消 override func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) self.initialTouchPoint = CGPoint.zero self.storkPhase = .notStarted self.trackedTouch = nil self.state = .cancelled } // 重置手势识别器 override func reset() { super.reset() self.initialTouchPoint = CGPoint.zero self.storkPhase = .notStarted self.trackedTouch = nil } }

使用自定义手势

func addCheckmarkGesture() { let checkmarkGesture = CheckmarkGestureRecognizer(target: self, action: #selector(handleCheckmarkGesture(_:))) view.addGestureRecognizer(checkmarkGesture) } @objc func handleCheckmarkGesture(_ gesture: CheckmarkGestureRecognizer) { print("handle gesture") }

具体详细解析Implementing a Discrete Gesture Recognizer

实现连续手势识别器

如果手势并不能容易的匹配具体模式,或者当你想使用手势来聚集多个触摸输入事件,可以自定义一个连续手势识别器。

下面同样实现一个连续手势识别器,如下图所示

屏幕快照 2019-04-17 下午3.50.18.png

功能实现

struct StorkeSample { let location: CGPoint init(location: CGPoint) { self.location = location } } class TouchCapureGesture: UIGestureRecognizer, NSCoding { var trackedTouch: UITouch? = nil var samples = [StorkeSample]() required init?(coder aDecoder: NSCoder) { super.init(target: nil, action: nil) self.samples = [StorkeSample]() } func encode(with aCoder: NSCoder) {} // 处理触摸事件 override func touchesBegan(_ touches: Set, with event: UIEvent) { // The gesture fails immediately if the initial event contains two touches if touches.count != 1 { self.state = .failed } // If there is only one touch, the touch object is saved in the trackedTouch property and the custom addSample helper method creates a new StrokeSample struct with the touch data if self.trackedTouch == nil { if let firstTouch = touches.first { self.trackedTouch = firstTouch self.addSample(for: firstTouch) self.state = .began } } else { // ignore all but the first touch for touch in touches { if touch != self.trackedTouch { self.ignore(touch, for: event) } } } } func addSample(for touch: UITouch) { let newSample = StorkeSample(location: touch.location(in: self.view)) self.samples.append(newSample) } // record each new sample and update the gesture recognizer’s state. override func touchesMoved(_ touches: Set, with event: UIEvent) { self.addSample(for: touches.first!) state = .changed } // Setting the state to UIGestureRecognizer.State.ended is equivalent to setting the state to recognized and results in a call to the gesture recognizer’s action method. override func touchesEnded(_ touches: Set, with event: UIEvent) { self.addSample(for: touches.first!) state = .ended } // Resetting the Gesture Recognizer // Always implement the touchesCancelled(_:with:) and [reset()] methods in your gesture recognizers and use them to perform any cleanup override func touchesCancelled(_ touches: Set, with event: UIEvent) { self.samples.removeAll() state = .cancelled } override func reset() { self.samples.removeAll() self.trackedTouch = nil } }

实现与离散基本差不多,具体解释可以参考Implementing a Continuous Gesture Recognizer

实际场景问题

1、同时识别多个手势

碰到了多个手势的识别问题,需要实现UIGestureRecognizerDelegate协议中的 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool方法来实现多个手势识别器的共同识别。

func addGestures() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleGestures(_:))) panGesture.delegate = self view.addGestureRecognizer(panGesture) let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleGestures(_:))) swipeGesture.delegate = self view.addGestureRecognizer(swipeGesture) } @objc func handleGestures(_ gesture: UIGestureRecognizer) { print("gesture: \(gesture)") } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true }

可以看到拖动的时候,还识别了轻扫手势:

gesture: gesture:

2、UIGestureRecognizer多层视图中的触发问题

场景:在一个Superview中,添加了一个Subview。Tap一下Superview,将Subview颜色修改

lazy var testView: UIView = { let view = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 200)) view.backgroundColor = UIColor.red return view }() override func viewDidLoad() { super.viewDidLoad() let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapEvent)) view.addGestureRecognizer(tapGesture) view.addSubview(testView) } @objc func handleTapEvent() { print("chang color") testView.backgroundColor = UIColor.yellow }

虽然功能实现很简单,但是当我点击Subview的时候,颜色同样会改变,这其实并不是我所希望的,为了解决这个问题,我们可以实现代理,实现接受触摸方法func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool

设置代理

tapGesture.delegate = self

实现代理方法

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { let touchPoint = touch.location(in: view) return !testView.frame.contains(touchPoint) }

可以对每一个tap操作touchPoint进行判断,如果touchPoint在subview上,返回false,让UIGestureRecognizer不做任何操作,不触发其action。该方法的默认返回值为true。

完全可以对每一个tap操作touchPoint进行判断,如果touchPoint 在subview上,完全可以返回False,让UIGestureRecognizer不做任何操作,不触发其action。该方法的默认返回值为True。

3、UIControl类或者子类控件事件与手势重叠,先响应控件事件

对于 UIButton等控件进行单击操作,如果父视图有点击手势需要识别,依然会按照响应链来处理,先响应这些控件的单击事件。

func buttonAndGestureTestExample() { let tapGesturer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))) view.addGestureRecognizer(tapGesturer) let button = UIButton(type: .custom) button.frame = CGRect(x: 100, y: 100, width: 100, height: 100) button.backgroundColor = UIColor.red button.addTarget(self, action: #selector(buttonClicked(_:)), for: .touchUpInside) view.addSubview(button) } @objc func buttonClicked(_ button: UIButton) { print("button clicked") } @objc func handleTapGesture(_ gesture: UITapGestureRecognizer) { print("handleTapGesture") }

点击按钮,将直接触发按钮事件

button clicked 参考

UIGestureRecognizer UIGestureRecognizer Tutorial: Getting Started



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3