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

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

  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,欢迎继续关注! :)

我的牛客网账号是917470656,上面有我记录的几篇面经。

个人公众号:iOS开发学习

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

#iOS开发工程师##iOS开发工程师实习生##iOS工程师##iOS开发实习生##学习秋招#
iOS开发学习 文章被收录于专栏

学习iOS开发 == 手握大厂offer

全部评论

相关推荐

joecii:如果没有工资,那可能没有工资是这家公司最小的问题了
找实习记录
点赞 评论 收藏
分享
02-01 12:05
复旦大学 Java
腾讯的提前批大概率应该是没有笔试的,但是这个时候有相当部分的同学简历估计都没有准备好,没准备好的同学也不用急,大部分都是3月之后开,这个时候开的绝大多数都是神仙打架,问的东西也比较难,打算投递的同学也多看下计算机网络和操作系统,腾讯对这部分的知识问的比较多。另外多刷下牛客的热门题库,刷题注意刷ACM模式,和牛客的周赛题,腾讯有的部门会从这里面出原题。我是@程序员花海关注我,带你了解更多校招资讯!
程序员花海:还没有来得及准备的同学可以看下学习路线:https://www.nowcoder.com/discuss/824693499982315520?sourceSSR=users算法题:https://www.nowcoder.com/feed/main/detail/20e7a999fa04485b88340a274411ca0d?sourceSSR=users八股文:https://www.nowcoder.com/discuss/833102362771251200?sourceSSR=users简历书写方式:https://www.nowcoder.com/discuss/839907820706205696?sourceSSR=users都是以前在牛客发的文章~
软开人,秋招你打算投哪些...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务