iOS UITouch事件解决-原理篇
前言:
事件 是界面交互(或者人机交互)的最基本组成之一。没有它手机上的App就失去了存在的意义。
一个事件的周期:事件的产生——事件的传递——事件的响应
事件传递到响应实现原理:hitTest:withEvent: 和pointInside:withEvent 和touches方法
事件传递和事件响应区别:
事件的传递是自上而下(父控件到子控件);事件的响应是自下而上(顺着响应者链条向上传递:子控件到父控件。
iOS中的事件类型
- 触摸事件(touch events)
- 按压事件(press events)
- 摇晃事件(shake – motions events)
- 远程控制事件 (remote – controls events)
- 编辑菜单消息事件(editing menu messages)
一、事件的产生和传递
- 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中为什么是队列而不是栈?由于队列的特点是先进先出,先产生的事件先解决才符合常理,所以把事件增加到队列。
- UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便解决,通常,先发送事件给应使用程序的主窗口(keyWindow)。
- 主窗口会在视图层次结构中找到一个最合适的视图来解决触摸事件,这也是整个事件解决过程的第一步。
找到合适的视图控件后,就会调使用视图控件的touches方法来作具体的事件解决。
1.1 事件传递原理
事件传递是通过下面两个方法完成的:
hitTest:withEvent: (UIView 的实例方法)
pointInside:withEvent (UIView 的事例方法)
传递过程:
- 主窗口接收到应使用传过来的事件后,首先判断能不能接收事件。假如能,就会判> 断触摸点在不在自身范围内。(通过pointInside:withEvent判断)假如不在,就不解决。假如在就进行第二步。
- 假如触摸点在自己的坐标范围内,那么窗口会从后往前遍历自己的子控件,来寻觅最合适的view。(通过hitTest:withEvent:方法递归寻觅)
- 遍历到每一个子控件后,又会重复上面的两个步骤(传递事件给子控件,1.判断子控件是否接受事件,2.点在不在子控件上)
- 按照上面步骤循环遍历子控件,直到找到最合适的view,假如没有更合适的子控件,那么自己就是最合适的view。
UIView不能接收事件的三种情况:1. 不允许交互 `userInteractionEnabled` = NO2. 透明度 `alpha` < 0.013. 父视图或者者子视图的 `hidden` = YES
1.2 hitTest:withEvent:代码具体实现
// 什么时候调使用:只需事件一传递给一个控件,那么这个控件就会调使用自己的这个方法// 作使用:寻觅并返回最合适的view// UIApplication -> [UIWindow hitTest:withEvent:]寻觅最合适的view告诉系统// point:当前手指触摸的点// event:触摸事件- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ // 判断自己是否接收事件 if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES) { return nil; } // 判断点是不是在当前视图上 if (![self pointInside:point withEvent:event]) { return nil; } // 从后往前遍历自己的子控件,寻觅更合适的View for (long i = self.subviews.count - 1; i >= 0; i--) { // 获取子控件 UIView *childView = self.subviews[i]; // 将自己坐标系的点转化成子控件坐标系的点 CGPoint childPoint = [self convertPoint:point toView:childView]; // 递归调使用hitTest方法,寻觅更加合适的View UIView *fitView = [childView hitTest:childPoint withEvent:event]; if (fitView) { return fitView; } } // 没有找到比自己更适合的View return self;}// 作使用:判断下传入过来的点在不在方法调使用者的坐标系上// point:是方法调使用者坐标系上的点- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ return NO;}
1.3 重写 hitTest:withEvent: 方法实使用之处
1.3.1 阻拦事件的解决
- 正由于hitTest:withEvent:方法可以返回最合适的view,所以可以通过重写hitTest:withEvent:方法,返回指定的view作为最合适的view。
- 不论点击哪里,最合适的view都是hitTest:withEvent:方法中返回的那个view。
- 通过重写hitTest:withEvent:,即可以阻拦事件的传递过程,想让谁解决事件谁就解决事件。
栗子:
//BView是一个view的子视图//BView是UIButton的父视图,但是BView的userInteractionEnable= NO//通过hitTest可以阻拦事件的功能 即可以指定UIButton作为响应者。- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ for (UIView *v in self.subviews) { if ([v isKindOfClass:NSClassFromString(@"BView")]) { for (UIView *vv in v.subviews) { if ([vv isKindOfClass:NSClassFromString(@"UIButton")]) { CGPoint insideP = [self convertPoint:point toView:vv]; BOOL isInsidePoint = [vv pointInside:insideP withEvent:event]; if (isInsidePoint) { return vv; break; } } } break; } } return self;}
1.3.2 return nil作使用
return nil的含义:
hitTest:withEvent:中return nil的意思是调使用当前hitTest:withEvent:方法的view不是合适的view,子控件也不是合适的view。
特殊使用法
谁都不能解决事件,窗口也不能解决。
重写window的hitTest:withEvent:方法return nil
只能有窗口解决事件。
控制器的view的hitTest:withEvent:方法return nil或者者window的hitTest:withEvent:方法return self
二、事件的响应
2.1响应者对象(UIResponder)
iOS中,我们最常接触的事件是触摸事件,什么样的对象才能解决触摸事件呢?
只有继承UIResponder的类或者者子类才可以响应和解决事件。由于UIResponder:是使用于响应和解决事件的笼统对象。
具体可以解决触摸事件的类如下:
- UIApplication
- UIViewController (包括它的子类)
- UIView (包括它的子类)
2.2 下面是UIResponder类提供的响应和解决触摸事件的4方法:
//开始接触屏幕,就会调使用一次- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;//手指开始移动就会调使用(这个方法会频繁的调使用,其实一接触屏幕就会屡次调使用)- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;//手指离开屏幕时,调使用一次- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;//触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,或者者view上面增加手势时,系统会自动调使用view的下面方法- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
通过这4个方法即可以知道触摸事件的整个过程。四个方法中的两个参数UITouch 和 UIEvent 解释下:
UITouch对象
- 当使用户使用一根手指触摸屏幕时,会创立一个与手指相关联的UITouch对象
- 一根手指对应一个UITouch对象
UITouch作使用
- 保存着跟手指相关的信息,比方触摸的位置、时间、阶段
- 当手指移动时,系统会升级同一个UITouch对象,使之能够一直保存该手指在的触 摸位置
- 当手指离开屏幕时,系统会销毁相应的UITouch对象
UITouch的主要属性和方法
//触摸产生时所处的窗口@property(nonatomic,readonly,retain) UIWindow *window; //触摸产生时所处的视图@property(nonatomic,readonly,retain) UIView *view; //短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或者更多的点击@property(nonatomic,readonly) NSUInteger tapCount; //记录了触摸事件产生或者变化时的时间,单位是秒@property(nonatomic,readonly) NSTimeInterval timestamp; //当前触摸事件所处的状态@property(nonatomic,readonly) UITouchPhase phase;//获取手指与屏幕的接触半径 IOS8以后可使用 只读@property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);//获取手指与屏幕的接触半径的误差 IOS8以后可使用 只读@property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);//触摸时所在的窗口 只读@property(nullable,nonatomic,readonly,strong) UIWindow *window;//触摸时所在视图@property(nullable,nonatomic,readonly,strong) UIView *view;//获取触摸手势@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);//获取触摸压力值,一般的压力感应值为1.0 IOS9 只读@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);//获取最大触摸压力值@property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);//获得在指定视图的位置// 返回值表示触摸在view上的位置// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0,0))// 调使用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置- (CGPoint)locationInView:(nullable UIView *)view;//该方法记录了前一个触摸点的位置- (CGPoint)previousLocationInView:(nullable UIView *)view;
UIEvent 对象
每产生一个事件,就会产生一个UIEvent对象
UIEvent:称为事件对象,记录事件产生的时刻和类型
主要属性和方法
//事件类型 @property(nonatomic,readonly) UIEventType type;//事件子类型 @property(nonatomic,readonly) UIEventSubtype subtype;//事件产生的时间@property(nonatomic,readonly) NSTimeInterval timestamp;//返回值:返回与接收器相关联的所有触摸对象。- (nullable NSSet <UITouch *> *)allTouches; // 返回值:返回属于一个给定视图的触摸对象,使用于表示由接收器所表示的事件。- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view; //返回值:返回属于一个给定窗口的接收器的事件响应的触摸对象。- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;//返回值:返回触摸对象被传递到特殊手势识别- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture
终于说完了UITouch 和 UIEvent ,其实还有很多细节,这里就不开展说了,回到touches的四个方法上来说事情:
- touches 里包含了一个或者多UITouch对象,也即一个或者多个手指同时触摸view,因而touches.count就是触摸的点数,是1就是单点触摸,大于1就是多点触摸。
- 一次触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
假如两根手指同时触摸一个view,那就是一个事件,view只会调使用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象- 假如这两根手指一前一后分开触摸同一个view,那就是两个事件,view会分别调使用2次touchesBegan:withEvent:方法,并且每次调使用时的touches参数中只包含一个UITouch对象
响应者链传递过程示用意:
响应者链视图.png
以上四个方法就是view使用来响应事件而后做出解决的过程。
注意事项:
- 在view上用这四个方法,必需自己设置view而后重写父类方法。
- 在viewController里,重写这四个方法,是响应viewController的事件,viewController上的子视图需要响应和解决事件需要自己设置,不要搞混淆了,以为在viewController里面写,就是重写了view的这四个方法。
view重写touches方法,解决事件的缺陷
1. 必需自己设置View2. 因为是View内部的touches方法中监听触摸事件,因而默认情况下无法让其余外界对象监听View的触摸事件3. 不容易区分使用户的具体手势行为。
用技巧:
如何做到一个事件多个对象解决?
由于系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象解决的目的
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ // 1.自己先解决事件... NSLog(@"do somthing..."); // 2.再调使用系统的默认做法,再把事件交给上一个响应者解决 [super touchesBegan:touches withEvent:event]; }
本文借鉴了少量前辈的文章,假如有不对的地方请指正,欢迎大家一起交流学习。
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS UITouch事件解决-原理篇
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS UITouch事件解决-原理篇