iOS中事件分为
- 触摸事件
- 加速计事件
- 远程控制事件
响应者对象
能够接收并处理事件的对象,必须是UIResponder
的子类。比如:UIApplication
、UIWindow
、UIView
等。
UIResponder处理事件的几种方法
1、触摸事件
// 一根或者多根手指开始触摸
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手机在屏幕上移动,只要移动就会触发
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手指离开屏幕
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 结束触摸前,由系统强制退出或者来电,事件会被取消
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 预估触摸特性,当从边缘进入,系统不知道当前触摸特性,会被调用下列方法
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches;
2、加速计事件
// 开始动起来
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
// 运动结束
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
// 运动被打断,取消
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
3、远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
UITouch
- 触摸事件中,touches中存放的是UITouch对象,一根手指对应一个UITouch对象
- 触摸开始,系统会创建手指对应的UITouch对象,随着手指移动,UITouch对象会随着更新,触摸事件结束,系统会销毁这个UITouch对象
1、属性
// 触摸产生时对应触摸的窗口
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
// 触摸产生时对应的触摸视图
@property(nullable,nonatomic,readonly,strong) UIView *view;
// 触摸事件产生或者变化的时间(单位秒)
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 当前触摸事件的状态(开始:Began、移动:Moved、静止:Stationary、结束:Ended、取消Cancelled)
@property(nonatomic,readonly) UITouchPhase phase;
// 短时间的触摸次数,据此判断是几根手指触摸,双击、单击或者其他更多
@property(nonatomic,readonly) NSUInteger tapCount;
// 触摸手势组
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers;
# pragma mark - iOS8新增属性
// 触摸的主半径
@property(nonatomic,readonly) CGFloat majorRadius;
// 触摸主半径的公差
@property(nonatomic,readonly) CGFloat majorRadiusTolerance;
# pragma mark - iOS9新增属性
// 触摸类型(直接手指触摸:Direct、间接触摸:Indirect、触摸笔触摸:Stylus)
@property(nonatomic,readonly) UITouchType type;
// 触摸力度,其中1.0表示平均力压值
@property(nonatomic,readonly) CGFloat force;
// 能触摸的最大力度
@property(nonatomic,readonly) CGFloat maximumPossibleForce;
// 高角度,仅适用于用触摸笔触摸类型。0:触摸笔平行于平面;M_PI/2:触摸笔垂直于平面
@property(nonatomic,readonly) CGFloat altitudeAngle;
// 预估更新索引,可以让更新的触摸对象与原对象进行关联,当前触摸对象的触摸特性变化,该值就会加一
@property(nonatomic,readonly) NSNumber * _Nullable estimationUpdateIndex;
// 预估属性,当前触摸对象的预估触摸特性(力度:Force、方位角:Azimuth、高度:Altitude、位置:Location)
@property(nonatomic,readonly) UITouchProperties estimatedProperties;
// 预期将要更新的特性,如果没有更新,则当前值为最终预估值,用于从边缘进入时系统开始无法获得准确的touches值
@property(nonatomic,readonly) UITouchProperties estimatedPropertiesExpectingUpdates;
2、方法
// 返回触摸在view上的坐标位置(以view左上角原点(0, 0)为准),若传入的view = nil,则返回触摸点在window上的位置
- (CGPoint)locationInView:(nullable UIView *)view;
// 同上,这个方法记录了上一个触摸点的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;
# pragma mark - iOS9新增方法
// 精准的返回触摸在view上的坐标位置
- (CGPoint)preciseLocationInView:(nullable UIView *)view;
// 精准的返回上一个在view上的触摸位置
- (CGPoint)precisePreviousLocationInView:(nullable UIView *)view;
// 返回触摸在view上的方位角(仅适用于触摸笔触摸事件)
- (CGFloat)azimuthAngleInView:(nullable UIView *)view;
// 返回触摸在view上的指向方向角的单位向量(仅适用于触摸笔触摸事件)
- (CGVector)azimuthUnitVectorInView:(nullable UIView *)view;
UIEvent
- 每产生一个事件就会产生一个UIEvent对象
- UIEvent对象记录了事件的产生时刻和类型
1、属性
// 事件类型(Touches:点击、Motion:加速计、RemoteControl:远程控制、Presses:按压)
@property(nonatomic,readonly) UIEventType type;
/**
同一事件类型的具体事件
None:type是触摸
MotionShake:type是加速计
== 剩下是针对远程控制事件类型 ==
RemoteControlPlay:播放
RemoteControlPause:暂停
RemoteControlStop:停止
RemoteControlTogglePlayPause:切换播放暂停
RemoteControlNextTrack:下一曲
RemoteControlPreviousTrack:上一曲
RemoteControlBeginSeekingBackward:开始往后寻找(开始快退)
RemoteControlEndSeekingBackward:结束向后寻找(结束快退)
RemoteControlBeginSeekingForward:开始向前寻找(开始快进)
RemoteControlEndSeekingForward:结束向前寻找(结束快进)
*/
@property(nonatomic,readonly) UIEventSubtype subtype;
// 事件产生的时间
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 事件中产生的所有触摸点
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
2、方法
// 返回在窗口上的所有触摸点
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
// 返回在当前视图上的所有触摸点
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
// 返回事件中手势的触摸点
- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture
# pragma mark - iOS9新增方法
// 将丢失的触摸点放在一个数组中返回(手机扫描渲染是每秒60帧,会出现绘画延迟,所以有些触摸点会丢失,下列方法是针对这种情况的优化)
- (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch;
// 预测的一组触摸点位置,但是不定和实际触摸点一样,所以是预估值
- (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch;
不能接收事件情况:
- 不能接收用户交互,self.userInteractionEnabled = NO
- 被隐藏,self.hidden = YES
- 透明度小于0.01,self.alpha < 0.01
- 如果父控件不能接收触摸事件,那么子控件也将不能接收
触摸事件中事件的产生和传递
1、事件发生后,系统会将事件加入到UIApplication管理的队列中
2、UIApplication遵循先进先出的原则,将队列中先进的事件发送给主窗口(keyWindow)
3、主窗口会在视图层级中找到一个合适的view来接收和处理事件
4、找到合适的view后,会调用视图的touches方法来具体处理事件
具体寻找合适的view
1、先判断view是否能接收触摸事件
2、触摸点是否在view身上
3、在子控件数组中从后向前遍历子控件,然后重复上面两步,找到则返回合适的子控件
4、没找到合适的子控件,则自己就是合适处理的
选出最佳view方法
// 事件一传递给控件,就会调用下列方法,返回最合适接收事件的UIView
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// 默认值是 YES,返回触摸点是否在调用者身上
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
事件传递详细过程
1、触摸事件产生和传递后,找到最佳接收和处理事件的view
2、插入说明:响应者链条由事件响应者组成
3、如果最佳视图调用了[super touches...],则事件由下往上顺着响应者链传递上去
4、接着就会调用上一个响应者的touches...方法
5、如果上一个响应者不能响应,则传递给上上一个响应者
6、如果传递到UIApplication还是不能处理,那这个时间就会被废弃
判断上一个响应者
- 如果当前视图是视图控制器的view,则上一个响应者是该视图的视图控制器
- 如果当前视图没有视图控制器,则上一个响应者是该视图的父视图
实例
所属关系:
self.view:子控件灰色
灰色:子控件先添加红色,后添加绿色
绿色:子控件先添加橘色,后添加白色
符合:满足可接收事件条件,自身能接收事件,触摸点在自己身上
否:不能接收事件
点击红色:UIApplication -> UIWindow -> 灰色(符合)-> 查询灰色子控件(红色、绿色)-> 绿色(触摸点不在身上,否)-> 红色(符合)
点击白色:UIApplication -> UIWindow -> 灰色(符合)-> 查询灰色子控件(红色、绿色)-> 绿色(符合)-> 查询绿色子控件(白色、橘色)-> 橘色(触摸点不在身上,否)-> 白色(符合)
点击橘色:UIApplication -> UIWindow -> 灰色(符合)-> 查询灰色子控件(红色、绿色)-> 绿色(符合)-> 查询绿色子控件(白色、橘色)-> 橘色(符合)
具体演示demo,请点击这里。