iOS学习笔记【二】——事件处理
只做简单笔记📝 详细请戳标题链接🔗
key point:iOS把用户触发事件打包成一个UIEvent对象,作为事件传递的消息载体,放入当前活跃的APP的消息队列中,然后通过Hit-Testing来找到响应者,响应者通过响应链的传递做出响应。
系统响应阶段
触摸事件从触屏产生后,由IOKit生成一个 IOHIDEvent 事件,并传递给SpringBoard,再由SpringBoard通过 mach port 转发给App 进程。
主线程的runloop被唤醒,触发了source1回调。source1回调又触发了一个source0回调,source0回调将接收到的IOHIDEvent对象封装成UIEvent对象,此时APP将正式开始对于触摸事件的响应。
UIEvent
UIEvent包含最常见的三种事件:Touch Events(触摸事件)、Motion Events(运动事件,比如重力感应和摇一摇等)、Remote Events(远程事件,比如用耳机上的按键来控制手机)。
UIResponder响应者对象
在UIKit中,UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,所以这些类都可以接收并处理响应事件。UIResponder内部提供了以下方法来处理事件触摸事件:
// 触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
// 加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
// 远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
UITouch对象
- 保存着跟手指相关的信息,比如触摸的位置、时间、阶段
- 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
- 当手指离开屏幕时,系统会销毁相应的UITouch对象
- 一根手指对应一个UITouch对象
- 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan: withEvent:方法,touches参数中装着2个UITouch对象
触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view;
短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;
返回值表示触摸在view上的位置,调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
(CGPoint)locationInView:(UIView *)view;
该方法记录了前一个触摸点的位置
(CGPoint)previousLocationInView:(UIView *)view;
响应链相关
// 下一响应者
- (UIResponder *)nextResponder;
// 判定对象是否是第一响应者
- (BOOL)isFirstResponder;
// 判断对象是否允许成为第一响应者
- (BOOL)canBecomeFirstResponder;
// 成为第一响应者
- (BOOL)becomeFirstResponder;
// 判断对象是否允许放弃第一响应者
- (BOOL)canResignFirstResponder;
// 放弃第一响应者
- (BOOL)resignFirstResponder;
事件的传递
触摸事件的传递是从父控件传递到子控件,也就是UIApplication->window->寻找处理事件最合适的view,如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件
UIView不能接收触摸事件的三种情况:
- 不允许交互:userInteractionEnabled = NO(UIImageView、UILabel默认为NO)
- 隐藏:如果把父控件隐藏hidden,那么子控件也会隐藏,隐藏的控件不能接受事件
- 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。
hitTest:withEvent:
- 能否处理触摸事件
- 触摸点是否在边界范围内:调用pointInside:withEvent:方法判断点在不在当前view上
传递过程
- UIApplication接收到事件,将事件传递给keyWindow。
- keyWindow遍历subViews的hitTest:withEvent:方法,找到点击区域内合适的视图来处理事件。
- UIView的子视图也会从后往前遍历其subViews的hitTest:withEvent:方法,以此类推。
- 直到找到点击区域内,且处于最上方的视图,将视图逐步返回给UIApplication。
- 在查找第一响应者的过程中,已经形成了一个响应者链。
- 应用程序会先调用第一响应者处理事件。
- 如果第一响应者不能处理事件,则调用其nextResponder方法,一直找响应者链中能处理该事件的对象。
- 最后到UIApplication后仍然没有能处理该事件的对象,则该事件被废弃。
Q&A
- 如何通过一个view查找它所在的viewController——响应链,nextResponder
- 对于一个超出父View范围的子View,如何保证其响应事件——重写父视图的
hitTest:withEvent:
方法
先酱 后续补上手势识别的部分(TODO)