响应者链条

** 响应者链条**


屏幕快照 2016-03-26 下午6.38.21.png
  • 在iOS中不是任何对象都能处理时间,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”
  • UIApplication UIViewController UIView都是继承自UIResponder,因此他们都是响应者对象,都能够接收并处理事件。
  • 当用户点击按钮,系统最先响应这个点击事件,由AppDelegate接收,然后不断上抛直到到达UIButton,如图中实线所示。当UIButton接收到这个事件,UIResponder相应的方法开始工作,系统默认做法是调用控件对应的super方法,值得一提的是:super会调用父类中的方法,而此处刚好会调到当前控件父控件所对应的方法。此时的事件传递方向刚好相反,从UIButton向底层抛,直到系统接收并做出响应,如图中虚线所示。这一来一回,我们称之为响应者链条。
  • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)。主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理。

UIView不接收触摸事件的三种情况

  • 不接收用户交互
    userInteractionEnabled = NO;
  • 隐藏
    hidden = YES;
  • 透明
    alpha = 0.0 ~ 0.01;
    如果一个控件的父控件隐藏了,则子控件也将被隐藏

UIResponder内部提供了以下方法来处理事件

响应者链条_第1张图片
屏幕快照 2016-03-26 下午6.46.49.png

  • UIView中的方法
    继承关系:UIWindow\UIButton -> UIView -> UIResponder -> NSObject
    上文中响应者链条提到过,系统接收用户点击事件后会将事件不断上抛,直到到达UIButton,那么这中间又是那些东西在运作?
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event ;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

这两个不太常见的方法其实是属于UIView的。

如何找到最合适的控件来处理事件

  • 流程

    • 判断当前控件userInteractionEnabled、hidden、alpha这三个属性的值( 自己是否能接收触摸事件)
    • 调用 pointInside: withEvent: ( 触摸点是否在自己身上)
    • 从后向前遍历子控件,并调用子控件的 hitTest: withEvent: 和 pointInside: withEvent: 方法(从后往前遍历子控件,重复前面两个步骤(遍历子控件时,先取出最后添加的那个子控件(因为在最前面))
  • 代码

    • hitTest和point方法
/*在AppDelegate中自定义UIWindow*/
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //自定义window
    self.window = [[LTYWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor redColor];
    //加载storyboard
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    
    //加载箭头指向的控制器
    UIViewController *vc = [storyboard instantiateInitialViewController];
    
    //设置窗口的根控制器
    self.window.rootViewController = vc;
    
    [self.window makeKeyAndVisible];
    
    return YES;
}
/**
调用事件:当事件传递给控件的时候,就会调用控件的这个方法,去寻找最合适的view
作用:寻找最合适的view
point:当前的触摸点,point这个点的坐标系就是方法调用者
*/
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //调用系统的做法取寻找最合适的view,返回最合适的view
    UIView *fitView = [super hitTest:point withEvent:event];
    NSLog(@"%s-----%@",__func__,fitView);
    return fitView;
}
/**作用:判断当前这个点在不在方法调用者(控件)上*/
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return YES;
}

  • 事件的产生和传递
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%@",[self class]);
}

//UIApplication -> [UIWindow hitTest:wthTest:withEvent:] -> [subView hitTest:wthTest:withEvent:]
//因为所有的视图类都是继承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //1.判断当前控件能接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
 
    //2.判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    
    //3.从后往前遍历自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger 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) { //寻找最合适的view
            return fitView;
        }    
    }
    //循环结束,表示没有比自己更适合的view
    return self;
}

你可能感兴趣的:(响应者链条)