# iOS 响应者链及事件传递

498 阅读3分钟

UIResponder

在 iOS中,所有响应事件处理事件的对象都直接或间接的继承UIResponder。 在 iOS 视图结构中,呈现出来的是一个 N 叉树的树形结构,每个视图都只有一个父视图,可以有多个子视图。

寻找第一响应者

当用户点击某个视图或者按钮的时候,会首先响应 applocation 中的 UIWindow 一层一层的向下查找,直到找到点击的 view,这阶段用到的两个方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;   // default returns YES if point is in bounds

例如有这样一个图层结构:

在上图中用户点击视图中的ViewD时,UIWindow首先接收到响应,此响应包括用户点击的区域和一个封装好的UIEvent对象,然后UIWindow通过这些信息利用以下方法查找:

  1. UIWindow会通过调用pointInside:withEvent:方法返回的YES得知用户点击的范围在ViewA中;

  2. ViewA调用hitTest:withEvent:方法,在方法中遍历所有的subView(ViewB、ViewC)调用hitTest:withEvent:方法;

  3. 在遍历中发现使用ViewC调用pointInside:withEvent:方法时返回YES,得知用户点击在ViewC范围之内;

  4. ViewC调用hitTest:withEvent:方法,在方法中遍历所有的subView(ViewD、ViewE)调用hitTest:withEvent:方法;

  5. 在遍历中发现使用ViewD调用pointInside:withEvent:方法时返回YES,得知用户点击在ViewD范围之内;

  6. 在ViewD调用hitTest:withEvent:方法之前发现View的subViews的count为0,故确定用户点击在ViewD之上。

UIWindow会用遍历subviews,使用每一个subview调用hitTest:withEvent:方法,如果用户点击在某一个subview上,pointInside:withEvent:方法返回YES,再用这个subview调用hitTest:withEvent:方法,依次类推,直到当前view没有子view或点击的位置没有在其任何子view之上,便确定用户点击在某view上

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    for (UIView *view in self.subviews) {
        if([view pointInside:point withEvent:event]){
            UIView *hitTestView = [view hitTest:point withEvent:event];
            if(nil == hitTestView){
                return view;
            }
        }
    }
    return nil;
}

1.Alpha=0、子视图超出父视图的情况、userInteractionEnabled=NO、hidden=YES视图会被忽略,不会调用hitTest 2.父视图被忽略后其所有子视图也会被忽略 3.出现视图无法响应的情况,可以考虑上诉情况来排查问题

##事件传递 事件传递的顺序其实就是寻找第一响应者的逆序,当找到了第一响应(View,button)之后,响应者就要对事件作出响应,这里就是使用了 UIResponder 的 nextResponder 方法。

nextResponder 的规则

因为UIView和UIViewController都是继承于UIResponder,所以在调用nextResponder时有几条规则如下:

  1. 当一个view调用其nextResponder会返回其superView;

  2. 如果当前的view为UIViewController的view被添加到其他view上,那么调用nextResponder会返回当前的UIViewController,而这个UIViewController的nextResponder为view的superView;

  3. 如果当前的UIViewController的view没有添加到任何其他view上,当前的UIViewController的nextResponder为nil,不管它是keyWinodw或UINavigationController的rootViewController,都是如此;

  4. 如果当前application的keyWindow的rootViewController为UINavigationController(或UITabViewController),那么通过调用UINavigationController(或UITabViewController)的nextResponder得到keyWinodw;

  5. keyWinodw的nextResponder为UIApplication,UIApplication的nextResponder为AppDelegate,AppDelegate的nextResponder为nil。

通过知道了上述规则,便可以通过用户点击的ViewD,查看ViewD是否响应了点击事件,如果没有找它的nextResponder,如果没有再继续找,直到找到AppDelegate再没有响应,则此点击事件被系统丢弃,大致流程如下:

事件响应链的大致流程就是如此了: 1.先向下寻找(第一响应者) 2,再向上寻找 (事件传递)

参考: www.cnblogs.com/easy-coding…

developer.apple.com/documentati…