App优化

iOS事件传递和响应链

2019-06-10  本文已影响0人  CodeRookie

前言

看关于这方面的文章基本没有能涉及到UIGestureRecognizers相关的文章,因此决定写这样一篇文章。也是我的第一篇文章,如有什么不对请及时指正。
本文主要通过一些实际测试来便于大家理解。

正文

事件传递和响应链流程图.png

1.hitTest:withEvent:和pointInside

1.1 hitTest:withEvent:和pointInside 演练

1.2 hitTest:withEvent:内部实现代码还原

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"-----%@",self.nextResponder.class);
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
    //判断点在不在这个视图里
    if ([self pointInside:point withEvent:event]) {
        //在这个视图 遍历该视图的子视图
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            //转换坐标到子视图
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            //递归调用hitTest:withEvent继续判断
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                //在这里打印self.class可以看到递归返回的顺序。
                return hitTestView;
            }
        }
        //这里就是该视图没有子视图了 点在该视图中,所以直接返回本身,上面的hitTestView就是这个。
        NSLog(@"命中的view:%@",self.class);
        return self;
    }
    //不在这个视图直接返回nil
    return nil;
}

1.3 pointInside运用:增大热区范围

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    NSLog(@"%@ -- pointInside",self.class);
    CGRect bounds = self.bounds;
    //若原热区小于200x200,则放大热区,否则保持原大小不变
    //一般热区范围为40x40 ,此处200是为了便于检测
    CGFloat widthDelta = MAX(200 - bounds.size.width, 0);
    CGFloat heightDelta = MAX(200 - bounds.size.height, 0);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    return CGRectContainsPoint(bounds, point);
    
}

2.响应链

2.1 响应链的组成

响应链.png

还用上面那个栗子:
点击redView:
redview -> grayView -> viewController -> ...


touches事件测试结果.png

因为只实现到controller的touches事件方法因此只打印到Controller。

2.2 UIControl阻断响应链

把上面栗子中的grayView替换成一个Button:

    GSExpandButton *expandButton = [[GSExpandButton alloc] initWithFrame:CGRectMake(0, kTopHeight, kScreenWidth/2, 400)];
    expandButton.backgroundColor = [UIColor lightGrayColor];
    [expandButton setTitle:@"点我啊" forState:UIControlStateNormal];
    [expandButton addTarget:self action:@selector(expandButtonClick:) forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:expandButton];
    
    self.redView = [[GSRedView alloc] initWithFrame:CGRectMake(0, 0, expandButton.bounds.size.width / 2, expandButton.bounds.size.height / 3)];
    [expandButton addSubview:self.redView];
    
    self.blueView = [[GSBlueView alloc] initWithFrame:CGRectMake(expandButton.bounds.size.width/2, expandButton.bounds.size.height * 2/3, expandButton.bounds.size.width/2, expandButton.bounds.size.height/3)];
    
    //    blueView.userInteractionEnabled = NO;
    //    blueView.hidden = YES;
    //    blueView.alpha = 0.1;//0.0;
    [expandButton addSubview:self.blueView];
    
    self.yellowView = [[GSYellowView alloc] initWithFrame:CGRectMake(CGRectGetMinX(expandButton.frame), CGRectGetMaxY(expandButton.frame) + 20, expandButton.bounds.size.width, 100)];
    [self.view addSubview:self.yellowView];

点击redView:
redview -> expandButton


UIControl阻断touches事件传递测试结果.png

2.3UIScrollView阻断响应链

self.grayView = [[GSGrayView alloc] initWithFrame:CGRectMake(0, kTopHeight, kScreenWidth, kScreenHeight * 3 / 4)];
    [self.view addSubview:self.grayView];
    
    self.tableView = [[GSTableView alloc] initWithFrame:CGRectMake(0, 20, kScreenWidth, self.grayView.bounds.size.height / 2)];
    self.tableView.dataSource = self;
    self.tableView.backgroundColor = [UIColor darkGrayColor];
    self.tableView.delegate = self;
    [self.grayView addSubview:self.tableView];
    
    self.redView = [[GSRedView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth/2, self.tableView.bounds.size.height/2)];
    [self.tableView addSubview:self.redView];
UIScrollView阻断响应链.png

点击redview
redview -> tableView


UIScrollView阻断响应链结果.png

3.手势

3.1手势的探索以及和touch事件的关系

在上面栗子中的view增加gestureRecognizer:

- (void)addGesture {
    GSGrayGestureRecognizer *grayGesture = [[GSGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    [self.grayView addGestureRecognizer:grayGesture];
    
    GSRedGestureRecognizer *redGesture = [[GSRedGestureRecognizer alloc] initWithTarget:self action:@selector(redViewClick:)];
    [self.redView addGestureRecognizer:redGesture];
    
    GSBlueGestureRecognizer *blueGesture = [[GSBlueGestureRecognizer alloc] initWithTarget:self action:@selector(blueViewClick:)];
    [self.blueView addGestureRecognizer:blueGesture];
}

点击redView
打印结果如下图所示:


gesture响应结果.png
// default is YES. causes touchesCancelled:withEvent: or pressesCancelled:withEvent: to be sent to the view for all touches or presses recognized as part of this gesture immediately before the action method is called.
@property(nonatomic) BOOL cancelsTouchesInView; 

// default is NO.  causes all touch or press events to be delivered to the target view only after this gesture has failed recognition. set to YES to prevent views from processing any touches or presses that may be recognized as part of this gesture      
@property(nonatomic) BOOL delaysTouchesBegan;         

 // default is YES. causes touchesEnded or pressesEnded events to be delivered to the target view only after this gesture has failed recognition. this ensures that a touch or press that is part of the gesture can be cancelled if the gesture is recognized
@property(nonatomic) BOOL delaysTouchesEnded;        

3.2手势和UIControl的关系

// button在上面
    self.grayView = [[GSGrayView alloc] initWithFrame:CGRectMake(0, kTopHeight, kScreenWidth/2, 400)];
    GSGrayGestureRecognizer *graygesture = [[GSGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    [self.grayView addGestureRecognizer:graygesture];
    [self.view addSubview:self.grayView];
    
    GSExpandButton *expandButton = [[GSExpandButton alloc] initWithFrame:CGRectMake(30, kTopHeight, 100, 100)];
    expandButton.backgroundColor = [UIColor redColor];
    [expandButton setTitle:@"点我啊" forState:UIControlStateNormal];
    [expandButton addTarget:self action:@selector(expandButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.grayView addSubview:expandButton];
  ![手势和UIControl的关系.png](https://img.haomeiwen.com/i2452209/1016b9c7d7cbc1c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

点击button


button上gesture下点击结果.png UIControl验证一.png

点击greenView


UIControl验证一结果.png

3.3 手势和UIScrollView的关系

self.grayView = [[GSGrayView alloc] initWithFrame:CGRectMake(0, kTopHeight, kScreenWidth, kScreenHeight * 3 / 4)];
    GSGrayGestureRecognizer *grayGesture = [[GSGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    //    grayGesture.delaysTouchesBegan = YES;
    //    grayGesture.cancelsTouchesInView = NO;
    //    grayGesture.delaysTouchesEnded = YES;
    [self.grayView addGestureRecognizer:grayGesture];
    [self.view addSubview:self.grayView];
    
    self.tableView = [[GSTableView alloc] initWithFrame:CGRectMake(0, 20, kScreenWidth, self.grayView.bounds.size.height / 2)];
    self.tableView.dataSource = self;
    self.tableView.backgroundColor = [UIColor darkGrayColor];
    self.tableView.delegate = self;
    [self.grayView addSubview:self.tableView];
  ![UIScrollView点击事件探索.png](https://img.haomeiwen.com/i2452209/6af7b895293d42e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

点击tableView
当父控件没有手势时


UIScrollView点击事件探索结果.png

当父控件有手势时


UIScrollView点击事件探索结果2.png
UIScrollView点击穿透解决方案

当UIScrollView为最优响应者父控件有手势时,UIScrollView及其子类的点击代理方法和touchesBegan方法不响应。

解决方法:三种解决方式

  1. 可以通过给父控件手势设置cancelsTouchesInView为NO,则会同时响应gestureRecognizer的事件和UIScrollView及其子类的代理方法和touches事件。

  2. 给父控件中的手势的代理方法里面做一下判断,当touch的view是我们需要触发的view的时候,return NO ,这样就不会走手势方法,而去触发这个touch.view这个对象的方法了。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([NSStringFromClass([touch.view class])    isEqualToString:@"UITableViewCellContentView"]) {
        return NO;
    }
    return YES;
}
  1. 可以通过给UIScrollView及其子类添加gestureRecognizer,从而来调用需要处理的事情。

文章若有不对地方,欢迎批评指正

上一篇 下一篇

猜你喜欢

热点阅读