UIScrollView拾遗

2018-10-15  本文已影响61人  高浩浩浩浩浩浩

前几天遇到一个bug在一个collectionView上嵌套collectionView的页面, 下拉刷新以后会出现cell的点击事件全部不响应的问题. 这个问题查了很久才找到原因, 这里记录一下.


IMG_0050.PNG
思路过程:

最开始遇到这个问题,以为是collectionView的frame错误导致的, 因为我们知道如果subView的位置超出了parentView的frame, 就会出现subView的点击事件不响应的问题。

但是排查以后发现collectionViewframe并没有问题, 而且虽然刷新以后cell不可以点击, 但是collectionView仍然可以滑动, 并且在滑动以后cell就可以恢复响应点击事件了。

随后发现注释掉加在collectionView上面的自定义手势后, bug就不会出现了. 然后把问题归结为自定义手势和collectionView的手势冲突导致的. 这里把自定义手势的cancelsTouchesInView 设置为NO, 发现问题仍然存在,而且如果没有响应MJRefresh的刷新时间,单独的滑动并不会产生这个问题。

这里又把问题转向了MJRefresh的代码,查看源码发现当MJRefreshHeader的state 是 Refreshing的时候,在设置scrollView的contentInset以后又设置了contentOffset。 代码如下:

    dispatch_async(dispatch_get_main_queue(), ^{
        [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
            CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
            // 增加滚动区域top
            self.scrollView.mj_insetT = top;
            // 设置滚动位置
            CGPoint offset = self.scrollView.contentOffset;
            offset.y = -top;
            [self.scrollView setContentOffset:offset animated:NO];
        } completion:^(BOOL finished) {
            [self executeRefreshingCallback];
        }];
     });

然后注释掉 [self.scrollView setContentOffset:offset animated:NO]; 或者把这行代码替换为 self.scrollView.contentOffset = offset; 也不会出现这个bug了。
这里大概知道了,因为某种原因,scrollView在进入tracking状态以后,结束下拉刷新以后并没有退出tracking状态, 所以导致cell的点击事件都被暂停掉了,这时候再滑动scrollView, 才会让tracking状态改为NO。 cell响应恢复正常。

但是如果这里直接修改第三方的代码,会让后面第三方库的维护变的十分不方便。所以这里继续查是否还有其他方法可以解决这个问题。这时候发现scrollView有canCancelContentTouchesdelaysContentTouches 这两个属性,将这两个属性设置为NO以后cell恢复响应,这里以为问题到此结束,准备开开心心的提交代码。 结果在重新过了一遍其他相关功能的时候发现,在编辑状态拖拽cell结束的时候,cell会响应tap点击删除的动作。问题仍然存在。。。。
最后在下拉刷新结束以后调整contentSize, 发现可以改变scrollView的tracking状态。然后也不会造成其他的影响, 问题到这里算是暂时结束。

知识点

1.UIScrollViewtracking状态

tracking 表示scrollView正在被跟踪,从你的手指touch屏幕开始,scrollView开始一个timer,如果:

  1. 150ms内如果你的手指没有任何动作,消息就会传给subView。
  2. 150ms内手指有明显的滑动(一个swipe动作),scrollView就会滚动,消息不会传给subView,这里就是产生问题二的原因。
  3. 150ms内手指没有滑动,scrollView将消息传给subView,但是之后手指开始滑动,scrollView传送touchesCancelled消息给subView,然后开始滚动。

观察下tableView的情况,你先按住一个cell,cell开始高亮,手不要放开,开始滑动,tableView开始滚动,高亮取消。

delaysContentTouches的作用:

这个标志默认是YES,使用上面的150ms的timer,如果设置为NO,touch事件立即传递给subview,不会有150ms的等待。

cancelsTouches的作用:

这个标准默认为YES,如果设置为NO,这消息一旦传递给subview,这scroll事件不会再发生。

cancelsTouchesInView的作用:
文档上是这么描述的:

A Boolean value affecting whether touches are delivered to a view when a gesture is recognized.
通过设置这个布尔值,来设置手势被识别时触摸事件是否被传送到视图

通过设置这个布尔值,来设置手势被识别时触摸事件是否被传送到视图。
举个🌰

- (void)viewDidLoad {
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
    button.backgroundColor = [UIColor colorWithRed:0.1 green:0.5 blue:0.4 alpha:1];
    [supView addSubview:button];
    [button addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapAction:)];
    tap.cancelsTouchesInView = NO;
    [button addGestureRecognizer:tap];
}

- (void)tapAction:(UITapGestureRecognizer *)sender {
    NSLog(@"tap");        
}

- (void)btnAction:(UIButton *)btn {
    NSLog(@"button");    
}

cancelsTouchesInViewNO的时候,会分别触发tapAction:btnAction:方法;而当cancelsTouchesInViewYES的时候,只会触发tapAction:方法。

所以开始的时候,尝试把cancelsTouchesInView设置为NO,希望让collectionView同时相应自定义的手势和系统的点击事件。

2. setContentOffset: animated:setContentOffset:的区别

如果animated这一参数设置为NO,或者直接设置contentOffset这个property,delegate会收到一个scrollViewDidScroll:消息。如果animated这一参数设置为YES,则在整个动画过程中,delegate会收到一系列的scrollViewDidScroll:消息,并且当动画完成时,还会收到一个scrollViewDidEndScrollingAnimation:消息。

实验证明,使用setContentOffset:animated:方法得到的回调行为和官方文档中描述的一致。而使用UIView动画,则只能收到一次scrollViewDidScroll:回调,不能收到scrollViewDidEndScrollingAnimation:回调。

所以在把MJRefreshHeadersetContentOffset:animated:方法注释掉以后, 因为scrollViewtracking状态没有变化,也不会出现bug


参考资料:
UIScrollView的delaysContentTouches与canCencelContentTouches属性

上一篇下一篇

猜你喜欢

热点阅读