iOS探究:UI视图之事件传递&视图响应
事件传递
事件传递的两个核心方法
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds第一个方法返回的是一个UIView,是用来寻觅最终哪一个视图来响应这个事件
第二个方法是用来判断某一个点击的位置能否在视图范围内,假如在就返回YES
事件传递的流程
WX20181205-193658@2x.png
流程形容
我们点击屏幕产生触摸事件,系统将这个事件加入到一个由UIApplication管理的事件队列中,UIApplication会从消息队列里取事件分发下去,首先传给UIWindow
在UIWindow中就会调用hitTest:withEvent:方法去返回一个最终响应的视图
在hitTest:withEvent:方法中就回去调用pointInside: withEvent:去判断当前点击的point能否在UIWindow范围内,假如是的话,就会去遍历它的子视图来查找最终响应的子视图
遍历的方式是使用倒序的方式来遍历子视图,也就是说最后增加的子视图会最先遍历,在每一个视图中都回去调用它的hitTest:withEvent:方法,可以了解为是一个递归调用
最终会返回一个响应视图,假如返回视图有值,那么这个视图就作为最终像是试图,结束整个事件传递;假如没有值,那么就会将UIWindow作为响应者
hitTest:withEvent:
WX20181205-201230@2x.png
流程形容
首先会判断当前视图的hiden属性、能否可以交互以及透明度能否大于0.01,假如满足条件则进入下一步,否则返回nil
调用pointInside: withEvent:方法来判断这个点能否在当前视图范围内,假如满足条件则进入下一步,否则返回nil
而后以倒序的方式遍历它的子视图,在每个子视图中去调用hitTest:withEvent:方法,假如有一个子视图返回了一个最终的响应视图,那么就将这个视图返回给调用方;假如一律遍历完成都没有找到一个最终的响应视图,由于点击位置在当前视图范围内,就将当前视图作为最终响应视图返回
实例场景
接下来我们通过一个具体的实例来进一步的了解事件传递,例如:在一个方形按钮中点击中间的圆形区域有效,而点击四角无效
按钮图片.png
核心思想是在pointInside: withEvent:方法中修改对应的区域
代码如下:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.userInteractionEnabled || [self isHidden] || self.alpha <= 0.01) { return nil; } //判断当前视图能否在点击范围内 if ([self pointInside:point withEvent:event]) { //遍历当前对象的子视图(倒序) __block UIView *hit = nil; [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { //坐标转换 CGPoint convertPoint = [self convertPoint:point toView:obj]; //调用子视图的hitTest方法 hit = [obj hitTest:convertPoint withEvent:event]; //假如找到了就中止遍历 if (hit) *stop = YES; }]; //返回当前的视图对象 return hit?hit:self; }else { return nil; }}- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { CGFloat x1 = point.x; CGFloat y1 = point.y; CGFloat x2 = self.frame.size.width / 2; CGFloat y2 = self.frame.size.height / 2; //判断能否在圆形区域内 double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); if (dis <= self.frame.size.width / 2) { return YES; } else{ return NO; }}视图的响应者链
首先我们要知道事件传递和响应过程是相反的
假如hitTest:withEvent:找到了第一响应者initial view,但是该响应者没有解决该事件,那么事件会沿着响应者链向上传递:第一响应者 -> 父视图 -> 视图控制器,假如传递到最顶级视图还没解决事件,那么就传递给UIWindow去解决,若window对象也不解决那么就交给UIApplication解决,假如UIApplication对象还不解决,就丢弃该事件(但是并不会引起崩溃)
并且在iOS中,能够响应事件的对象都是UIResponder的子类对象,UIResponder提供了四个客户点击的回调方法,分别对应客户点击开始、移动、点击结束以及取消点击,其中只有在程序强制退出或者者来电时,取消点击事件才会调用。
系统回调方法
// UIView是UIResponder的子类,可以覆盖下列4个方法解决不同的触摸事件// 一根或者者多根手指开始触摸view,系统会自动调用view的下面方法- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event// 一根或者者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event// 一根或者者多根手指离开view,系统会自动调用view的下面方法- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event// 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event// 提醒:touches中存放的都是UITouch对象响应者链流程图
WX20181205-205455@2x.png
GitHub
Demo
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS探究:UI视图之事件传递&视图响应