UIScrollView的滚动和触摸

一、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事件沿着消息响应链传递; 例如:scrollView上添加一个按钮,返回NO的话,scrollView将不会将触摸事件传递给按钮,按钮的event事件就不响应。
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
返回YES 在这个view上取消进一步的touched消息(不在这个view上处理,事件传到下一个view)。如果这个参数view不是一个UIControl对象,默认返回YES。如果是一个UIControl 对象返回NO。 返回NO它会停止拖动,将触摸事件传递给子实图例如scrollView上添加一个按钮,按钮是UIControl,返回NO,所以拖拽按钮将不会滚动,为了能继续滚动需要重写这个方法返回YES;另外当设置canCancelContentTouches = NO时,这个方法将不会被调用。

- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
     if ([view isKindOfClass:UIButton.class]) {
         return YES;
     }
     return [super touchesShouldCancelInContentView:view];
 }
例子
 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,183评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,850评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,766评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,854评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,871评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,457评论 1 311
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,999评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,914评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,465评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,543评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,675评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,354评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,029评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,514评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,616评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,091评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,685评论 2 360

推荐阅读更多精彩内容