UIScrollView的滚动和触摸

2021-09-09  本文已影响0人  冷武橘

一、UIScrollView原理

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

- (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;
}
小结: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
上一篇下一篇

猜你喜欢

热点阅读