美文网首页
UIScrollView拾遗

UIScrollView拾遗

作者: 高浩浩浩浩浩浩 | 来源:发表于2018-10-15 17:53 被阅读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参数,可以获得正确的UIScrollViewDelegate的回调;而使用UIView动画则不能
      苹果的官方文档中,对setContentOffset:animated:这一方法会引起的回调有大概如下的解释:

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

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

    • 使用animated参数,可以获取到动画过程中contentOffset的值
    • 使用animated参数,即使animated:NOscrollView也会进入tracking状态,而直接设置setContentOffset: 则不会。

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


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

    相关文章

      网友评论

          本文标题:UIScrollView拾遗

          本文链接:https://www.haomeiwen.com/subject/ziifzftx.html