美文网首页
UIScrollView的滚动和触摸

UIScrollView的滚动和触摸

作者: 冷武橘 | 来源:发表于2021-09-09 16:03 被阅读0次

    一、UIScrollView原理

    从你的手指touch屏幕开始,scrollView开始一个timer,如果:

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

    - (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event inContentView:(UIView *)view;
    系统默认是允许UIScrollView,按照消息响应链向子视图传递消息的。(即返回YES)。如果你不想UIScrollView的子视图接受消息,返回NO。当返回NO,表示UIScrollView接收这个滚动事件,不必沿着消息响应链传递了。如果返回YES,touches事件沿着消息响应链传递;
    - (BOOL)touchesShouldCancelInContentView:(UIView *)view
    返回YES 在这个view上取消进一步的touched消息(不在这个view上处理,事件传到下一个view)。如果这个参数view不是一个UIControl对象,默认返回YES。如果是一个UIControl 对象返回NO.

     MyScrollView *scrollView = [[MyScrollView alloc]init];
        [self.view addSubview:scrollView];
        scrollView.frame = CGRectMake(0, 0, self.view.bounds.size.width, 400);
        scrollView.backgroundColor = [UIColor redColor];
        scrollView.contentSize = CGSizeMake(0, self.view.frame.size.height);
        UIView *yellowview = [[GreenView alloc]init];
        yellowview.backgroundColor = [UIColor yellowColor];
        yellowview.frame = CGRectMake(100, 200, 200, 400);
        [scrollView addSubview:yellowview];
    
    @interface MyScrollView : UIScrollView
    @end
    @implementation MyScrollView
    - (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view{
        BOOL inContinue = [super touchesShouldBegin:touches withEvent:event inContentView:view];
        NSLog(@"是否将触摸事件传递给子控件:%s",__func__);
        return inContinue;
    }
    - (BOOL)touchesShouldCancelInContentView:(UIView *)view{
        BOOL cancel = [super touchesShouldCancelInContentView:view];
        NSLog(@"是否取消进一步的touched消息:%s",__func__);
        return cancel;
    }
    

    测试一:如果手指快速滑动yellowview ,很明显的滑动操作,控制台不会打印任何东西。touchesShouldBegin和touchesShouldCancelInContentView都不会执行。

    根据上面UIScrollView原理可知,150ms内手指有明显的滑动,scrollView就会滚动,消息不会传给subView。touchesShouldBegin系统默认是返回yes,也意味着只有消息传给subView时才会被触发。

    测试二:如果先触摸拖拽滑动yellowview,不明显的滑动操作。控制台会打印


    截屏2020-10-29 下午2.59.57.png

    根据上面UIScrollView原理可知,150ms内手指没有滑动之后手指开始滑动,scrollView传送touchesCancelled消息给subView,然后开始滚动。

    - (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view{
        NSLog(@"不否将触摸事件传递给子控件:%s",__func__);
        return NO;
    }
    
    • 如果你想直接拦截touch事件的传递,你直接返回NO就可以了。
    小结:1、一个scrollView只有是明显的滑动时,才不会将触摸事件传递给子控件,其他情况下都会将触摸事件传给子控件。
    2、直接重写touchesShouldBegin:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event inContentView返回NO时也不会将触摸事件传给子控件。
    3、当发生不明显的滑动,首先会将触摸事件传给子控件。但是当scrollView开始滑动时,scrollView传送touchesCancelled消息给subView又会取消触摸事件。

    二、delaysContentTouches和canCancelContentTouches

    @property(nonatomic) BOOL delaysContentTouches;   
    default is YES. if NO, we immediately call -touchesShouldBegin:withEvent:inContentView:. this has no effect on presses
    
    @property(nonatomic) BOOL canCancelContentTouches;    
     default is YES. if NO, then once we start tracking, we don't try to drag if the touch moves. this has no effect on presses
    

    delaysContentTouches的作用:
    这个标志默认是YES,使用上面的150ms的timer,如果设置为NO,touch事件立即传递给subView,不会有150ms的等待。默认YES;如果设置为NO,会马上执行touchesShouldBegin:withEvent:inContentView:(不管你滑得有多快,都能将事件立即传递给subView)

    canCencelContentTouches从字面上理解是“可以取消内容触摸“,默认值为YES。文档里的解释是这样的:翻译为中文大致如下:
    这个BOOL类型的值控制content view里的触摸是否总能引发跟踪(tracking)
    如果属性值为YES并且跟踪到手指正触摸到一个内容控件,这时如果用户拖动手指的距离足够产生滚动,那么内容控件将收到一个touchesCancelled:withEvent:消息,而scroll view将这次触摸作为滚动来处理。如果值为NO,一旦content view开始跟踪(tracking==YES),则无论手指是否移动,scrollView都不会滚动。

    简单通俗点说,如果为YES,就会等待用户下一步动作,如果用户移动手指到一定距离,就会把这个操作作为滚动来处理并开始滚动,同时发送一个touchesCancelled:withEvent:消息给内容控件,由控件自行处理。如果为NO,就不会等待用户下一步动作,并始终不会触发scrollView的滚动了。

    三、UIScrollView和hitTested-view

       MyScrollView*scrollView =[[MyScrollView alloc]init];
        scrollView.MyDelegate = self;
        [self.view addSubview:scrollView];
        scrollView.backgroundColor =[UIColor yellowColor];
        scrollView.frame = self.view.bounds;
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"点击了控制器");
    }
    

    根据开发经验我们可以清楚的知道,因为scrollView的存在,touchesBegan事件不会再被触发,很明显可以猜测到事件从window->scrollView,scrollView自己处理了touchesBegan:事件,并没有继续沿着响应链传递.

    为了让它继续沿着响应链传递,我们就可以这样

    @implementation MyScrollView
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [self.nextResponder touchesBegan:touches withEvent:event];
    }
    

    第二个例子:和上面基本上差不多的代码,只是添加了一个手势识别器

    - (void)viewDidLoad {
        [super viewDidLoad];
        UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
        [self.view addGestureRecognizer:gesture];
     
        MyScrollView*scrollView =[[MyScrollView alloc]init];
        [self.view addSubview:scrollView];
        scrollView.backgroundColor = [UIColor yellowColor];
        scrollView.frame = CGRectMake(0,0 , 100, 100);
    }
    - (void)tap:(UIGestureRecognizer*)geture{
        NSLog(@"测试");
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"点击了控制器");
    }
    

    这时你会发现,touchesBegan似乎又没响应了。很显然可以知道肯定是添加手势影响的。没错,你只需要再添上这一行就如之前一样了。

      gesture.cancelsTouchesInView = NO;
    

    下面我们来分析一下原因吧:

    UIScrollView 中有一个UIScrollViewDelayedTouchesBeganGestureRecognizer识别器,这个手势会截断hit-tested view事件并延迟0.15s才发送给hit-tested view。我们这里当点击屏幕时,首先会被gesture识别,而UIScrollViewDelayedTouchesBeganGestureRecognizer又会截断hit-tested view事件,当gesture识别完成后,hit-tested view事件也继续发送过去,就会被取消。当我们gesture.cancelsTouchesInView = NO;就不会再被取消,这样hit-tested view事件会继续沿着响应链进行传递和处理。

    实际应用:点击键盘收回键盘

    - (void)viewDidLoad {
        [super viewDidLoad];
        UITextField *textFiled =[[UITextField alloc]init];
        [self.view addSubview:textFiled];
       // textFiled.frame = CGRectMake(0, 0, 100, 40);
        textFiled.backgroundColor = [UIColor redColor];
        [textFiled  becomeFirstResponder];
        
        UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap)];
        [self.view addGestureRecognizer:gesture];
        gesture.cancelsTouchesInView = NO;
        
        UITableView *tableView = [[UITableView alloc]init];
        tableView.frame = self.view.bounds;
        [self.view addSubview:tableView];
        tableView.dataSource = self;
        tableView.delegate = self;
        tableView.estimatedRowHeight = 0 ;
        tableView.estimatedSectionHeaderHeight = 0;
        tableView.estimatedSectionFooterHeight = 0;
        [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    }
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
       
        return cell;
            
    }
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
        NSLog(@"%@",touch.view);
        return YES;
    }
    
    #pragma mark - 点击键盘收回键盘
    - (void)tap{
        [self.view endEditing:YES];
    }
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
        NSLog(@"点击cell");
    }
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return 10;
    }
    @end
    

    相关文章

      网友评论

          本文标题:UIScrollView的滚动和触摸

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