Objective-C基础(四)
这是OC基础的最后一个章节啦,这节主要给大家讲讲响应者链条。
1. 响应者链条
关于响应者链条,相信大家可能听说过这么一句话:事件由上往下传递,响应由下往上传递,那么这句话是什么意思呢?
我们知道,在写UI时,每个UI控件,或是UI视图,都是从最初的一个UIView上,不断调用addSubview方法,叠加在父view上,进行展示的。
例如,假设我们有下面这么一段代码:
UIView *view1, *view2; UIButton *btn1; view1 = [UIView new]; view2 = [UIView new]; btn1 = [UIButton new]; [view1 addSubview:view2]; [view2 addSubview:btn1];
显而易见,view2的父视图为view1,btn1的父视图为view2。
如果我们现在在btn1上有一个点击事件,那么这个点击事件会直接传递给btn1吗? 答案是否定的,因为事件是由上往下传递的,这个事件会先传递给view1,再传递给view2,最后传递给btn1。
事件传递主要依靠下面这个函数来实现:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // 3种状态无法响应事件,1.用户交互被禁用;2.当前视图被隐藏;3.当前视图透明度小于0.01(跟被隐藏了差不多) if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 触摸点若不在当前视图上则无法响应事件 if ([self pointInside:point withEvent:event] == NO) return nil; // 从后往前遍历子视图数组 int count = (int)self.subviews.count; // 子视图数目 for (int i = count - 1; i >= 0; i--) { // 获取子视图 UIView *childView = self.subviews[i]; // 坐标系的转换,把触摸点在当前视图上坐标转换为在子视图上的坐标 CGPoint childP = [self convertPoint:point toView:childView]; // 询问子视图层级中的最佳响应视图 UIView *fitView = [childView hitTest:childP withEvent:event]; if (fitView){ // 如果子视图中有更合适的就返回 return fitView; } } // 没有在子视图中找到更合适的响应视图,那么自身就是最合适的 return self; }
从函数中可以看出,事件传递的流程为:
- 如果当前视图无法响应事件,则返回nil
- 如果当前点击处在当前视图可响应范围之外,则返回nil
- 从后往前遍历子视图,如果子视图能够处理当前事件,则返回子视图
- 否则返回自身视图
其中,第3步中,从后往前而不是从前往后遍历子视图的原因是:后加入的子视图会覆盖在先前加入的子视图之上,从用户角度来说,用户希望得到响应的视图应该是能够被看见的视图,而后加入的子视图因为会覆盖在最顶层所以更容易被用户看见,因此应该从后往前遍历。
此外,我们也应该注意pointInside: withEvent:这个方法,是很多面试官爱考的考点。 我们可以通过overwrite这个方法,来改变一个视图能够响应的范围(默认能够响应的范围是这个视图包含的屏幕区域)。
关于响应从下往上传递:我们在将事件从上往下传递后,利用- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event方法找到的最合适的响应视图,并不一定能够处理当前事件,仍按照上面的例子来说,假设我们有如下代码:
UIView *view1, *view2; UIButton *btn1; view1 = [UIView new]; view2 = [UIView new]; view2.userInteractionEnabled = YES; btn1 = [UIButton new]; [view1 addSubview:btn1]; [btn1 addSubview:view2];
注意与之前的区别,现在view2的父视图为btn1,而btn1的父视图为view1,view2和btn1的父子关系互相调换了。 此外,我们还开启了view2的用户交互属性。
如果我们现在在btn1上有一个点击事件,按照事件从上往下传递的流程,我们会找到view2。 然而,我们会发现view2并不能够处理这个点击事件,因此,这个点击事件便由下往上传递给了btn1,并交由btn1处理(btn1能够处理,则调用相应的响应方法进行处理)。 假设btn1仍然不能处理,则继续往上传递给view1,直至事件被处理或者最后被丢弃。
2. UIButton的继承关系
UIButton的继承关系为:
UIButton --> UIControl --> UIView --> UIResponder --> NSObject
我们要注意UIResponder和UIControl的区别:
- UIResponder可以响应某个事件,利用touchesBegan: withEvent:方法(自己的事情自己做)
- UIControl不仅本身可以响应某个事件,还可以利用addTarget: forSelector: withEvent:为指定的某个对象添加事件(交给别人来做)
例如,普通的UIView想要响应事件,只能依靠自身实现touchesBegan: withEvent:方法; 而UIButton想要响应事件,不仅可以依靠自身,还可以将这个事件绑定到一个目标对象上,依靠目标对象的某个方法来处理事件。
好啦,OC基础到这里就讲完啦,下期开始讲Runtime,欢迎继续关注! :)
我的牛客网账号是917470656,上面有我记录的几篇面经。
个人公众号:iOS开发学习
未经作者允许,禁止转载!
#iOS开发工程师##iOS开发工程师实习生##iOS工程师##iOS开发实习生##学习秋招#学习iOS开发 == 手握大厂offer