Objective-C基础(四)

2,054 阅读4分钟

这是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;
}

从函数中可以看出,事件传递的流程为:

  1. 如果当前视图无法响应事件,则返回nil
  2. 如果当前点击处在当前视图可响应范围之外,则返回nil
  3. 从后往前遍历子视图,如果子视图能够处理当前事件,则返回子视图
  4. 否则返回自身视图

其中,第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的区别:

  1. UIResponder可以响应某个事件,利用touchesBegan: withEvent:方法(自己的事情自己做
  2. UIControl不仅本身可以响应某个事件,还可以利用addTarget: forSelector: withEvent:为指定的某个对象添加事件(交给别人来做

例如,普通的UIView想要响应事件,只能依靠自身实现touchesBegan: withEvent:方法; 而UIButton想要响应事件,不仅可以依靠自身,还可以将这个事件绑定到一个目标对象上,依靠目标对象的某个方法来处理事件

好啦,OC基础到这里就讲完啦,下期开始讲Runtime,欢迎继续关注! :)

个人公众号:iOS开发学习

未经作者允许,禁止转载!