ios事件处理层级及响应链

事件的种类

运动事件:加速仪、陀螺仪、磁强仪等

远程控制事件:蓝牙耳机等

触摸事件:界面触摸等

界面是如何交互

当用户的手真正触摸到屏幕时,程序内部是如何响应的?

当触摸到屏幕时会生成一个touch Event(触摸事件),添加到UIapplication管理的事件队列中,UIApplication会从事件队列中(runLoop)中取出事件来分发到应响应的视图去处理。当触摸事件被UIApplication发出后,会从程序的keyWindow开始,然后依次向上传递,包括各种viewController和view,最后找到合适的处理该事件的视图来响应,称为事件传递

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

这两个方法是事件传递的关键所在。并且这两个是uiview的方法。但并非表明只有UIview才能响应事件传递,因为除了UIview,UIViewController也是可以响应事件传递的,多有它们拥有事件传递能力取决于它们共同的父类UIResponser。

而这两个方法又有什么关系

hitTest:(CGPoint)point withEvent:调用pointInside: withEvent:判断响应点是否在视图中,以便后续做出调整。

hitTest:(CGPoint)point withEvent:内部实现原理如下

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden) {
        return nil;
    }
    
    if ([self pointInside:point withEvent:event]) {  //发生在我的范围内
    
        //遍历子view
        NSArray *subViews = [[self.subviews reverseObjectEnumerator] allObjects];
        UIView *tmpView;
        
        for (UIView *subView in subViews) {
            //转换坐标系,然后判断该点是否在bounds范围内
            CGPoint convertedPoint = [self convertPoint:point toView:subView];
            //递归调用是否还有子视图
            tmpView = [subView hitTest:convertedPoint withEvent:event];
        }
        return tmpView?tmpView:self;
    } else {
        return nil;
    }
}

逻辑如下:

(1)首先会判断该视图自身能否处理该触摸事件(alphauserInteractionEnabledhidden

(2)调用pointInside:point withEvent:event判断该点是否在显示区域中,如果不在返回nil

(3)如果(2)是YES则倒叙遍历所有的子视图,(:subViews数组的排列顺序是按照子视图添加到父视图的顺序排列的),  并转化该点到子视图坐标系中。安装这样的顺序利用递归继续查询子视图的子视图,直到没有子视图了。返回最终的View,如果View不为nil,则返回最终可以响应的View

(4)如果(3)中没有子视图就返回自身

 触摸事件

自定义View,主要重写下面几个方法

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"%@ pointInside", self.BgColorString);
    return [super pointInside:point withEvent:event];
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
    return [super hitTest:point withEvent:event];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@ touchBegan", self.BgColorString);
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@ touchesMoved", self.BgColorString);
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@ touchesEnded", self.BgColorString);
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@ touchesCancelled", self.BgColorString);
    [super touchesCancelled:touches withEvent:event];
}


//创建一个UIview的分类
#import "UIView+Color.h"

@implementation UIView (Color)

- (NSString *)BgColorString
{
    if (self.backgroundColor == [UIColor redColor]) {
        return @"redColorView";
    } else if (self.backgroundColor == [UIColor blueColor]) {
        return @"blueColorView";
    } else if (self.backgroundColor == [UIColor yellowColor]) {
        return @"yellowColorView";
    } else if (self.backgroundColor == [UIColor lightGrayColor]) {
        return @"ligthGrayColorView";
    }
    return nil;
}

@end

 ViewController中的代码如下

    LightGrayView *grayView = [[LightGrayView alloc] initWithFrame:CGRectMake(50.f, 100.f, 260.f, 200.f)];
    
    RedView *redView = [[RedView alloc] initWithFrame:CGRectMake(0.f, 0.f, 120.f, 100.f)];
    
    BlueView *blueView = [[BlueView alloc] initWithFrame:CGRectMake(140.f, 100.f, 100.f, 100.f)];
    
    YellowView *yellowView = [[YellowView alloc] initWithFrame:CGRectMake(50.f, 360.f, 200.f, 200.f)];
    
    [self.view addSubview:grayView];
    [grayView addSubview:redView];
    [grayView addSubview:blueView];
    [self.view addSubview:yellowView];

 view是gray和yellow的父视图,gray是red和blue的俯视图

点击蓝色区域

2019-02-11 10:54:23.558114+0800 03-事件层次分析[2258:85276] -[YellowView hitTest:withEvent:]
2019-02-11 10:54:23.558333+0800 03-事件层次分析[2258:85276] yellowColorView pointInside
2019-02-11 10:54:23.558476+0800 03-事件层次分析[2258:85276] -[LightGrayView hitTest:withEvent:]
2019-02-11 10:54:23.558606+0800 03-事件层次分析[2258:85276] ligthGrayColorView pointInside
2019-02-11 10:54:23.558799+0800 03-事件层次分析[2258:85276] -[BlueView hitTest:withEvent:]
2019-02-11 10:54:23.558904+0800 03-事件层次分析[2258:85276] blueColorView pointInside
//---------------执行第二次查找-------------------
2019-02-11 10:54:23.559414+0800 03-事件层次分析[2258:85276] -[YellowView hitTest:withEvent:]
2019-02-11 10:54:23.559839+0800 03-事件层次分析[2258:85276] yellowColorView pointInside
2019-02-11 10:54:23.560197+0800 03-事件层次分析[2258:85276] -[LightGrayView hitTest:withEvent:]
2019-02-11 10:54:23.560952+0800 03-事件层次分析[2258:85276] ligthGrayColorView pointInside
2019-02-11 10:54:23.561762+0800 03-事件层次分析[2258:85276] -[BlueView hitTest:withEvent:]
2019-02-11 10:54:23.562306+0800 03-事件层次分析[2258:85276] blueColorView pointInside
//---------------查找成功,事件响应--------------
2019-02-11 10:54:23.563408+0800 03-事件层次分析[2258:85276] blueColorView touchBegan
2019-02-11 10:54:23.564020+0800 03-事件层次分析[2258:85276] ligthGrayColorView touchBegan
2019-02-11 10:54:23.564400+0800 03-事件层次分析[2258:85276] -[FindingViewController touchesBegan:withEvent:]
2019-02-11 10:54:23.586767+0800 03-事件层次分析[2258:85276] blueColorView touchesMoved
2019-02-11 10:54:23.586969+0800 03-事件层次分析[2258:85276] ligthGrayColorView touchesMoved
2019-02-11 10:54:23.587085+0800 03-事件层次分析[2258:85276] -[EOCFindingViewController touchesMoved:withEvent:]
2019-02-11 10:54:23.587792+0800 03-事件层次分析[2258:85276] blueColorView touchesMoved
2019-02-11 10:54:23.587951+0800 03-事件层次分析[2258:85276] ligthGrayColorView touchesMoved
2019-02-11 10:54:23.588053+0800 03-事件层次分析[2258:85276] -[FindingViewController touchesMoved:withEvent:]
2019-02-11 10:54:23.593764+0800 03-事件层次分析[2258:85276] blueColorView touchesMoved
2019-02-11 10:54:23.646086+0800 03-事件层次分析[2258:85276] ligthGrayColorView touchesMoved
2019-02-11 10:54:23.646234+0800 03-事件层次分析[2258:85276] -[FindingViewController touchesMoved:withEvent:]
2019-02-11 10:54:23.646807+0800 03-事件层次分析[2258:85276] blueColorView touchesMoved
2019-02-11 10:54:23.646956+0800 03-事件层次分析[2258:85276] ligthGrayColorView touchesMoved
2019-02-11 10:54:23.647057+0800 03-事件层次分析[2258:85276] -[FindingViewController touchesMoved:withEvent:]
2019-02-11 10:54:23.649772+0800 03-事件层次分析[2258:85276] blueColorView touchesEnded
2019-02-11 10:54:23.649952+0800 03-事件层次分析[2258:85276] ligthGrayColorView touchesEnded
2019-02-11 10:54:23.650061+0800 03-事件层次分析[2258:85276] -[FindingViewController touchesEnded:withEvent:]

单看查找顺序:

  1. 首先uiapplication分配任务到uiwindow然后在到viewController中的view
  2. 而后view会以倒叙的方式遍历子视图,yellow是最后添加到view中的,所以yellow首先被打印出来,由于touches不在yellow视图中返回nil,
  3. 不在yellowView中,继续判断是否在其grayView 中,如果是在grayView 中,按照倒叙遍历其子视图。在grayView中,首先判断blueView,正好点击的是在blueView,响应事件。
  4. 从打印结果可以开出,执行了两次事件传递 

响应链:

继承自UIResponder的类都可以称为响应者。UIViewcontroller和view都是继承自UIResponder的子类,而UIResponder有一个属性

 @property(nonatomic, readonly, nullable) UIResponder *nextResponder; 

根据文档说明它会返回下一个响应者,如果没有就返回nil,这样以单向链表的形式形成了一个响应者链

在lldb中po出响应者链

手势事件

手势与pointInside以及hitTest有什么关系

自定义一个UIPanGestureRecognizer

#import <UIKit/UIKit.h>

@interface CustomGesture : UIPanGestureRecognizer

@end

#import "CustomGesture.h"
#import <UIKit/UIGestureRecognizerSubclass.h>

@implementation CustomGesture

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s", __func__);
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s", __func__);
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s", __func__);
    [super touchesCancelled:touches withEvent:event];
}

@end

给触摸事件的案例代码中的grayView添加手势

-(void)viewDidLoad{
    CustomGesture *tapGesture = [[CustomGesture alloc] initWithTarget:self action:@selector(panAction)];
    [grayView addGestureRecognizer:tapGesture];
}
- (void)panAction{
    NSLog(@"%s", __func__);
}

操作1:对grayView进行拖拽

2019-01-30 20:37:00.059244+0800 03-事件层次分析[29917:713057] -[CustomGesture touchesMoved:withEvent:]
2019-01-30 20:37:00.059682+0800 03-事件层次分析[29917:713057] -[GestureViewController panAction]
2019-01-30 20:37:00.061921+0800 03-事件层次分析[29917:713057] -[CustomGesture touchesMoved:withEvent:]
2019-01-30 20:37:00.062545+0800 03-事件层次分析[29917:713057] -[GestureViewController panAction]
2019-01-30 20:37:00.063004+0800 03-事件层次分析[29917:713057] -[CustomGesture touchesEnded:withEvent:]
2019-01-30 20:37:00.063576+0800 03-事件层次分析[29917:713057] -[GestureViewController panAction]

    成功识别出拖拽手势,触发了panAction方法

操作2:注掉grayView的 [super touchesBegan:touches withEvent:event];或者[super touchesMoved:touches withEvent:event];

2019-01-30 20:39:54.186647+0800 03-事件层次分析[29962:714843] ligthGrayColorView touchesMoved
2019-01-30 20:39:54.228515+0800 03-事件层次分析[29962:714843] -[CustomGesture touchesMoved:withEvent:]
2019-01-30 20:39:54.228938+0800 03-事件层次分析[29962:714843] ligthGrayColorView touchesMoved
2019-01-30 20:39:54.269257+0800 03-事件层次分析[29962:714843] -[CustomGesture touchesMoved:withEvent:]
2019-01-30 20:39:54.269517+0800 03-事件层次分析[29962:714843] ligthGrayColorView touchesMoved
2019-01-30 20:39:54.309081+0800 03-事件层次分析[29962:714843] -[CustomGesture touchesMoved:withEvent:]
2019-01-30 20:39:54.309360+0800 03-事件层次分析[29962:714843] ligthGrayColorView touchesMoved
2019-01-30 20:39:54.309752+0800 03-事件层次分析[29962:714843] -[CustomGesture touchesMoved:withEvent:]
2019-01-30 20:39:54.309988+0800 03-事件层次分析[29962:714843] ligthGrayColorView touchesMoved
2019-01-30 20:39:54.310390+0800 03-事件层次分析[29962:714843] -[CustomGesture touchesEnded:withEvent:]
2019-01-30 20:39:54.310862+0800 03-事件层次分析[29962:714843] ligthGrayColorView touchesEnded

touchesBegan:withEvent:被注释掉后,没有触发began方法,手势没有识别成功,panAction方法没有触发。

可以大致推出:手势识别与4个touch方法有关 

touchesBegan、touchesMoved、touchesEnded、touchesCancel的四个方法来识别

手势识别,touch依然能响应

手势识别后,touch方法又会怎样?默认情况下,手势识别成功后会取消touch方法。

- (void)createGesture
{
    RedView *redView = [[RedView alloc] initWithFrame:CGRectMake(100.f, 100.f, 100.f, 100.f)];
    [self.view addSubview:redView];
    
    CustomGesture *tapGesture = [[CustomGesture alloc] initWithTarget:self action:@selector(panAction)];
    
    //默认情况下,如果手势识别出来了,会cancel掉view的touch响应链

    ///手势识别,touch响应链依然工作
    tapGesture.delaysTouchesBegan = NO;  //yes view的点击事件会被挂起   ;默认NO
    tapGesture.cancelsTouchesInView = NO;  // 如果为NO,redView的touch不会取消;默认YES
    
    //默认情况,delaysTouchesBegan = NO ,cancelsTouchesInView = YES;
    //手势识别成功后view的touch事件不能响应,手势没有识别成功时,view的touch事件依然能响应
    
    //delaysTouchesBegan = YES ,cancelsTouchesInView = YES;
    //手势识别成功后view的touch事件不能响应,手势没有识别成功时,view的touch事件不能响应
    
    //delaysTouchesBegan = YES , cancelsTouchesInView = NO;
    //手势没有识别成功touch不能响应, 手势识别成功后view的touch事件能响应
    
     //delaysTouchesBegan = NO , cancelsTouchesInView = NO;
    //不管有没有成功view的touch事件能响应
    
    //cancelsTouchesInView = NO; touch事件都能响应
    
    [redView addGestureRecognizer:tapGesture];
}

- (void)panAction
{
    NSLog(@"%s", __func__);
}

手势小结:

1、手势和pointInside以及hitTest的关系:手势的识别,必须先通过pointInside、hitTest找到这个view

 2、通过hitTest、pointInside找到的view:如果view或者它的superView有手势事件,都会响应(父view有手势子view也会响应,子view有手势,父view不会响应)

 3、手势的种类怎么分辨出来:tap、pangesture、swipeGesture:根据手势的四个touch方法来识别出种类的

 4、手势和view的touch事件的关系:delayTouchBegin、cancelTouchInView

默认情况下,如果手势识别出来了,会cancel掉view的touch响应链

UIButton响应事件

自定义一个UIButton

#import <UIKit/UIKit.h>

@interface CustomButton : UIButton

@end

#import "CustomButton.h"

@implementation CustomButton

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
    return [super pointInside:point withEvent:event];
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);     
    return [super hitTest:point withEvent:event];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s", __func__);
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s", __func__);
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"%s", __func__);
    [super touchesCancelled:touches withEvent:event];
}
@end


//Controller代码
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    customBtn = [CustomButton buttonWithType:UIButtonTypeCustom];
    
    customBtn.frame = CGRectMake(100.f, 100.f, 100.f, 100.f);
    customBtn.backgroundColor = [UIColor redColor];
    [customBtn setTitle:@"Button" forState:UIControlStateHighlighted];
    [customBtn addTarget:self action:@selector(btnAction:withEvent:) forControlEvents:UIControlEventTouchUpInside];
    
//    [customBtn sendActionsForControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:customBtn];
}

- (void)btnAction:(CustomButton *)btn withEvent:(UIEvent *)event
{
    NSLog(@" btnAction点击事件测试");
}

点击按钮

2019-01-31 09:19:35.054427+0800 03-事件层次分析[31460:782348] -[CustomButton hitTest:withEvent:]
2019-01-31 09:19:35.054636+0800 03-事件层次分析[31460:782348] -[CustomButton pointInside:withEvent:]
2019-01-31 09:19:35.054882+0800 03-事件层次分析[31460:782348] -[CustomButton hitTest:withEvent:]
2019-01-31 09:19:35.055000+0800 03-事件层次分析[31460:782348] -[CustomButton pointInside:withEvent:]
2019-01-31 09:19:35.056763+0800 03-事件层次分析[31460:782348]  btnAction点击事件测试
2019-01-31 09:19:35.059205+0800 03-事件层次分析[31460:782348] -[CustomButton touchesMoved:withEvent:]
2019-01-31 09:19:35.061045+0800 03-事件层次分析[31460:782348] -[CustomButton touchesMoved:withEvent:]

button的点击和pointInside、hitTest有啥关系: 判断是否点击在button上,才能响应事件

如果注掉touchBegin、touchMoved的super方法,

输出

2019-02-11 14:36:29.896920+0800 03-事件层次分析[2726:132545] -[EOCCustomButton hitTest:withEvent:]
2019-02-11 14:36:29.897241+0800 03-事件层次分析[2726:132545] -[EOCCustomButton pointInside:withEvent:]
2019-02-11 14:36:29.897508+0800 03-事件层次分析[2726:132545] -[EOCCustomButton hitTest:withEvent:]
2019-02-11 14:36:29.897623+0800 03-事件层次分析[2726:132545] -[EOCCustomButton pointInside:withEvent:]
2019-02-11 14:36:29.919954+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.927791+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.935504+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.943352+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.951351+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.959305+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.967578+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.975115+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.982428+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:29.989983+0800 03-事件层次分析[2726:132545] -[CustomButton touchesMoved:withEvent:]
2019-02-11 14:36:30.002929+0800 03-事件层次分析[2726:132545] -[CustomButton touchesEnded:withEvent:]

btnAction:withEvent:方法不会被调用(即绑定的事件不会被调用),因此可以推断UIbutton的事件也是根据touch4个方法来判断的。因为UIButton的点击事件它是根据UIControlEvents来响应的,如果touch事件触摸不成功,UIButton不能确定手指触摸屏幕后UIControlEvents的状态。

UIButton提供了一个方法它会立即执行该方法,不需要去点击

sendActionsForControlEvents://会立即调用指定event的Action

那么UIButton添加了事件后,内部又如何去调用的呢?

在UIButton中,有一个方法

-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event

该方法会将一个Action交给UIApplication。

在自定义的Button中重写该方法

-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    [super sendAction:@selector(twoAction) to:self forEvent:event];
}

-(void)twoAction{
    NSLog(@"--------这是自定义Action-------------");
}

输出

2019-01-31 10:14:46.412136+0800 03-事件层次分析[32471:813678] -[CustomButton pointInside:withEvent:]
2019-01-31 10:14:46.414107+0800 03-事件层次分析[32471:813678] --------这是自定义Action-------------
2019-01-31 10:14:46.434974+0800 03-事件层次分析[32471:813678] -[CustomButton touchesMoved:withEvent:]
2019-01-31 10:14:46.442693+0800 03-事件层次分析[32471:813678] -[CustomButton touchesMoved:withEvent:]

截取输出中的一段,我们发现调用的是twoAction方法,因此可以猜测UIButton的事件是在-sendAction:action to:forEvent: 方法中交给UIApplication处理。

运用

1. UIButton超过frame的部分可以点击

重写UIButton的pointInside:withEvent:方法

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    NSLog(@"%s",__func__);
    //获取自身的origin坐标
    CGFloat originx = self.bounds.origin.x;
    CGFloat originy = self.bounds.origin.y;
    if (self.frame.size.width <  50) {
        originx -=  (50 - self.frame.size.width)/2;
    }
    
    if (self.frame.size.height < 50) {
         originy -=  (50 - self.frame.size.height)/2;
    }
    
    CGRect newFrame = CGRectMake(originx, originy, 50.f, 50.f);

    if (CGRectContainsPoint(newFrame, point)) {
        return YES;
    }
    
    return [super pointInside:point withEvent:event];
    
}

如果button的size小于{50,50},那么只要点击在{50,50}区域范围内的事件都能响应

2.UIView超过俯视图的部分也可以响应

redView是grayView的子视图

代码

    LightGrayView *lightView = [[LightGrayView alloc] initWithFrame:CGRectMake(100, 100, 80, 100)];
    [self.view addSubview:lightView];
    RedView *redView = [[RedView alloc] initWithFrame:CGRectMake(-50, 20, 200, 50)];
    [lightView addSubview:redView];

分别点击1、2、3处

点击1处:点1,不在grayView的范围内,所以-pointInside:withEvent:(UIEvent *)event返回NO,所以点击1处的redView的touch事件不会响应,lightgray也不会相应

2019-01-31 10:45:55.955275+0800 03-事件层次分析[32774:826613] -[LightGrayView hitTest:withEvent:]
2019-01-31 10:45:55.955418+0800 03-事件层次分析[32774:826613] ligthGrayColorView pointInside

点击2处:点2,在grayView的范围内,不在redView内,但是根据查找原则还是会去redView中判断,结果返回的是nil

2019-01-31 10:53:00.492739+0800 03-事件层次分析[32774:826613] -[LightGrayView hitTest:withEvent:]
2019-01-31 10:53:00.492947+0800 03-事件层次分析[32774:826613] ligthGrayColorView pointInside
2019-01-31 10:53:00.493057+0800 03-事件层次分析[32774:826613] -[RedView hitTest:withEvent:]
2019-01-31 10:53:00.493157+0800 03-事件层次分析[32774:826613] redColorView pointInside
2019-01-31 10:53:00.494600+0800 03-事件层次分析[32774:826613] ligthGrayColorView touchBegan
2019-01-31 10:53:00.501128+0800 03-事件层次分析[32774:826613] ligthGrayColorView touchesMoved
2019-01-31 10:53:00.519290+0800 03-事件层次分析[32774:826613] ligthGrayColorView touchesMoved
...

点击3处:点3,在两个View的内

2019-01-31 10:58:01.439849+0800 03-事件层次分析[32774:826613] -[LightGrayView hitTest:withEvent:]
2019-01-31 10:58:01.440037+0800 03-事件层次分析[32774:826613] ligthGrayColorView pointInside
2019-01-31 10:58:01.440150+0800 03-事件层次分析[32774:826613] -[RedView hitTest:withEvent:]
2019-01-31 10:58:01.440258+0800 03-事件层次分析[32774:826613] redColorView pointInside
2019-01-31 10:58:01.440532+0800 03-事件层次分析[32774:826613] -[LightGrayView hitTest:withEvent:]
2019-01-31 10:58:01.440669+0800 03-事件层次分析[32774:826613] ligthGrayColorView pointInside
2019-01-31 10:58:01.440790+0800 03-事件层次分析[32774:826613] -[RedView hitTest:withEvent:]
2019-01-31 10:58:01.440924+0800 03-事件层次分析[32774:826613] redColorView pointInside
2019-01-31 10:58:01.441729+0800 03-事件层次分析[32774:826613] redColorView touchBegan
2019-01-31 10:58:01.442024+0800 03-事件层次分析[32774:826613] ligthGrayColorView touchBegan
2019-01-31 10:58:01.462154+0800 03-事件层次分析[32774:826613] redColorView touchesMoved
2019-01-31 10:58:01.462303+0800 03-事件层次分析[32774:826613] ligthGrayColorView touchesMoved
2019-01-31 10:58:01.470262+0800 03-事件层次分析[32774:826613] redColorView touchesMoved

第一种方法:重写grayView的-hitTest:withEvent:方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    NSLog(@"%s",__func__);
    //逆序遍历子视图
    for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
        //将subview中的点转换到self坐标系中的点
        CGPoint covertPoint = [subView convertPoint:point toView:self];
        //调用父类的super方法,依据查找原理,返回被点击的view
        UIView *hitView = [super hitTest:covertPoint withEvent:event];
        if (hitView) {
            return hitView;
        }
    }
    return [super hitTest:point withEvent:event];
}

上述方法的关键是将点击在子视图上点转换到其父视图坐标系中的点。

点击1处:

2019-01-31 11:19:27.107500+0800 03-事件层次分析[33185:844039] -[LightGrayView hitTest:withEvent:]
2019-01-31 11:19:27.107778+0800 03-事件层次分析[33185:844039] -[RedView hitTest:withEvent:]
2019-01-31 11:19:27.107933+0800 03-事件层次分析[33185:844039] redColorView pointInside
2019-01-31 11:19:27.108278+0800 03-事件层次分析[33185:844039] -[LightGrayView hitTest:withEvent:]
2019-01-31 11:19:27.108390+0800 03-事件层次分析[33185:844039] -[RedView hitTest:withEvent:]
2019-01-31 11:19:27.108521+0800 03-事件层次分析[33185:844039] redColorView pointInside
2019-01-31 11:19:27.109464+0800 03-事件层次分析[33185:844039] redColorView touchBegan
2019-01-31 11:19:27.109650+0800 03-事件层次分析[33185:844039] ligthGrayColorView touchBegan
2019-01-31 11:19:27.145274+0800 03-事件层次分析[33185:844039] redColorView touchesMoved
2019-01-31 11:19:27.145440+0800 03-事件层次分析[33185:844039] ligthGrayColorView touchesMoved
2019-01-31 11:19:27.146162+0800 03-事件层次分析[33185:844039] redColorView touchesMoved
2019-01-31 11:19:27.146320+0800 03-事件层次分析[33185:844039] ligthGrayColorView touchesMoved

发现redView的touch事件成功响应了

第二种方法:重写grayView的- (BOOL)pointInside: withEvent:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
        CGPoint convertPoint = [self convertPoint:point toView:subView];
        if ([subView pointInside:convertPoint withEvent:event]) {
            return YES;
        }
    }
    
    return [super pointInside:point withEvent:event];
    
}

猜你喜欢

转载自blog.csdn.net/a1034386099/article/details/86980219