本文对Multitouch Events的机制进行讲解。
iOS系统的Events类型分为以下几种:Multitouch events, Accelerometer events, Remote control events。
Multitouch Events
在需要时,可以实现Touch方法,来控制Event的处理和传递,流程如下:
Creating a Subclass of UIResponder
要自定义Event的处理和传递,需要先继承这几个类:
- UIView
- UIViewController
- UIControl
- UIApplication/UIWindow
注意,接收Touches的View必须设置userInteractionEnabled为YES,并且必须是可见的,不能是透明或者隐藏的。
Implementing the Touch-Event Handling Methods in Your Subclass
继承后,需要实现的UIResponder的方法为:
1 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; |
注意,Cancellation是由于外部事件导致的,例如来电。
Tracking the Phase and Location of a Touch Event
可以通过UITouch获取到phase, tapCount, location, previous location以及timestamp。
Retrieving and Querying Touch Objects
UIView的multipleTouchEnabled属性默认为NO,这意外着无论同时有多少个Touch发生,当前View只接收第一个,因此可以通过anyObject来获取当前Touch:
1 | UITouch *touch = [touches anyObject]; |
如果需要获取不同对象上的Touch,可以通过以下方式:
1 | [event allTouches]; |
以绘制用户的手写轨迹为例:
1 | @property(nonatomic, assign) size_t handWriteID; |
Handling One Touch Gestures
(1) Tap
1 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { |
(2) Swipe
这里省略了中间点的判断,只简单判断开始和结束:
1 | #define HORIZ_SWIPE_DRAG_MIN 12 |
(3) Drag
1 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { |
Handling Multitouch Gestures
对于同时处理多个Touch事件,需要先设置UIView的multipleTouchEnabled属性默认为YES,然后通过CFDictionaryRef来跟踪每一个Touch的phases(状态)。
注意:用CFDictionaryRef,而不是NSDictionary来跟踪,原因是NSDictionary会复制它的keys,而UITouch并没有适配NSCopying协议。
下面以对比起始点和结束点的位置为例,先记录起始点位置:
1 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { |
对比结束点位置:
1 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { |
判断最后一根手势离开屏幕:
1 | - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { |
Specifying Custom Touch Event Behavior
自定义Touch Event行为,可以通过以下方式:
- 支持多个MultiTouch的事件分发:设置View的multipleTouchEnabled为YES;
- 限制单个View的事件分发:设置View的exclusiveTouch为NO,这意味着该View不能与其他View同时接受事件,如下图,如果先在B中触摸,再在A中触摸,则A不会收到事件,反之,则B不会收到事件,而B和C可以同时收到:
- 限制对SubViews的事件分发:重写hitTest:withEvents:方法,使得SubViews直接接收不到事件:
1 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event |
- 停止单个View的事件分发:设置View的userInteractionEnabled为NO;
- 停止App在一段时间内的事件分发:
1 | [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; |
Forwarding Touch Events
一般情况下,如果一个Response对象想要处理一个Touch,则该Touch的View对象必须指向该Response对象。如果想要转发Touch事件给其他View,则该Response对象必须是自定义的UIView子类。
转发Touch事件,有两种方式,一种是UIView通过在hit-testing方法中转发给SubViews,一种是重写UIWindow的sendEvents方法:
1 | - (void)sendEvent:(UIEvent *)event { |
Best Practices for Handling Multitouch Events
一些注意事项:
- 实现Cancelation方法,保存状态;
- 如果继承UIView, UIViewController和UIResponder:实现所有的Touches方法,不要调用super方法;
- 如果是继承其他的UIKit类:不需要实现所有的Touches方法,一定要调用super方法;
- 不要转发事件给其他的UIKit类对象,转发给UIView或子类对象,并且确保其可以接收转发的;
- 不要显示地通过nextResponder方法发送事件到Responder Chain中;
- 不要对Touch的坐标取整,因为这会导致精度丢失,在一般设备上,坐标系为320×480,而在高分辨率设备上,为640×960,这意味着可能边界存在着0.5个点的情况。