iOS响应者链、事件的传递

作者: RasonWu | 来源:发表于2017-03-31 20:40 被阅读654次

1、响应链的传递

Responder一点也不神秘————iOS用户响应者链完全剖析(建议全看)
看完上面一篇应该能完全熟悉了响应链的传递,自己可以打印一下响应链看看,代码如下:

- (IBAction)click:(id)sender {
    UIResponder *res = sender;
    
    while (res) {
        NSLog(@"*************************************\n%@",res);
        res = [res nextResponder];
    }
}

2、Hit-Test 机制

当用户触摸(Touch)屏幕进行交互时,系统首先要找到响应者(Responder)。系统检测到手指触摸(Touch)操作时,将Touch 以UIEvent的方式加入UIApplication事件队列中。UIApplication从事件队列中取出最新的触摸事件进行分发传递到UIWindow进行处理。UIWindow 会通过hitTest:withEvent:方法寻找触碰点所在的视图,这个过程称之为hit-test view。
hitTest 的顺序如下

UIApplication -> UIWindow -> Root View -> ··· -> subview

在顶级视图(Root View)上调用pointInside:withEvent:方法判断触摸点是否在当前视图内;

如果返回NO,那么hitTest:withEvent:返回nil;

如果返回YES,那么它会向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。

如果有subview的hitTest:withEvent:返回非空对象则A返回此对象,处理结束(注意这个过程,子视图也是根据pointInside:withEvent:的返回值来确定是返回空还是当前子视图对象的。并且这个过程中如果子视图的hidden=YES、userInteractionEnabled=NO或者alpha小于0.1都会并忽略);

如果所有subview遍历结束仍然没有返回非空对象,则hitTest:withEvent:返回self;

系统就是这样通过hit test找到触碰到的视图(Initial View)进行响应。

如果还不清楚Hit-Test 机制,看更加清晰的Hit-Test 机制(建议还不清楚的看)

Paste_Image.png

3、手势的原理及与touches系列的关系,具体的可以看iOS触摸事件传递响应之被忽视的手势识别器工作原理(建议不看也没关系,结论在下面了。)

简而言之,就是下面这幅图了。触摸事件会优先分发给附在view的手势,在这段延迟的期间,如果手势被识别,那么view的touches系列将被立刻取消,如果没有被识别,那么会继续我们所熟知的touches系列流程。


Paste_Image.png

4、实际开发中常见的相关问题

在实际开发中,经常会遇到视图没有响应的情况,特别是新手会经常搞不清楚状况。

一下是视图没有响应的几个情况:

1.userInteractionEnabled=NO;

2.hidden=YES;

3.alpha=0~0.01;

4.没有实现touchesBegan:withEvent:方法,直接执行touchesMove:withEvent:等方法;

5.目标视图点击区域不在父视图的Frame上 (superView背景色为clear Color的时候经常会忽略这个问题)。

5、手势代理

ios手势识别代理,看这个基本上就够了。引用文章中的一段话,如下:

  • 当时做项目时这个主控制器就是RootViewController,虽然用的是ScrollView但也没考虑到导航栏的手势返回的问题 ,现在做小区宝3.0的闪购订单,用之前的就有问题了。导航栏的返回手势用不了,根据响应者链和响应事件,手势被ScrollView识别了,就到不了导航的手势识别,所以导致无法手势返回。

我也曾经处理过这样的问题,不过我那时候是带有QQ的侧滑功能,主控制器用的View是ScrollView,导致不能侧滑。但是处理的方法都是一样的,自定义的ScrollView的代码重写gestureRecognizerShouldBegin方法如下,我是手势方向向右并且x轴起点小于60px的,让ScrollView的手势失效。这样就不会截获对应的事件了。但是其实看完上面,还有更简单的方法,就是让ScrollView的手势共存,但是这样可能会带来一些其它的问题。shouldRecognizeSimultaneouslyWithGestureRecognizer设置为true,不过应该要判断手势为UIScreenEdgePanGestureRecognizer时才return true,这样就可以了。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
    CGPoint location = [gestureRecognizer locationInView:self];
    
    NSLog(@"velocity.x:%f----location.x:%d",velocity.x,(int)location.x%(int)[UIScreen mainScreen].bounds.size.width);
    if (velocity.x > 0.0f&&(int)location.x%(int)[UIScreen mainScreen].bounds.size.width<60) {
        return NO;
    }
    return YES;
} 

案例分析

案例一

下面这种做法,除非你很熟悉,否则不要这么干。因为 [super touchesBegan:touches withEvent:event];会执行原来默认的操作,如果按钮本来就没有添加对应的事件。那么[[self nextResponder] touchesBegan:touches withEvent:event];和[super touchesBegan:touches withEvent:event];将会向下一响应者发送两次事件。

-(void)touchesBegan:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event{
    if (self.enableNextResponder) {
        [[self nextResponder] touchesBegan:touches withEvent:event];
        [super touchesBegan:touches withEvent:event];
    }
}

案例二

一个按钮添加了点击事件到底发生了什么事儿。
我们有时候需要使用到一些特殊的情况,比如:
1、A包含B,AB都响应事件。
对于普通View,根据响应链,让B作为第一个响应者处理,然后B根据nextResponder传递触摸事件。
针对手势做分析:
手势不会走view的touches系列方法,但有自己的一系列touches方法,不过没有暴露出来。但是shouldRecognizeSimultaneouslyWithGestureRecognizer也可以做到。

针对UIButton的分析:
UIButton addTarget分析,addTarget是UIControl的方法,其实addTarget的方法原理是,UIControl对touches的触摸事件的封装。[super touchesBegan:touches withEvent:event];包括了对
事件的封装处理,如果重新了[super touchesBegan:touches withEvent:event];,并且里面什么都不实现,那么当前UIButton添加的addTarget所绑定的所有事件都不会触发。因为覆盖了父类UIControl的封装方法。
如果我想一个按钮的事件触发,并且它的下一响应者也能触发相应的事件。那么该怎么处理呢?
我们在按钮上处理,重写touchesBegan系列的方法,那么根据上面所说,必须要调用super的方法,并且主动像下一响应者[self nextResponder]发送touchesBegan系列的方法。

2、A包含B、C,C在B的上面,但是想让B接收事件,C不接收事件
这种可以这么处理,自定义C的View,重写hitTest:withEvent方法,返回nil,这样自定义C的View及其子类都不会拦截事件。这样B就可以顺利处理事件。
还可以把C的userInteractionEnabled设置为NO

3、A是B、C的父视图,C在B的上面,这时候,CB都处理事件。这样到底行不行?根据响应链,这样应该是不靠谱的了。在C的touches方法中调用C的touches方法,然后重写B的touches方法,但是这样怪怪的。有什么高招也请多多指教。貌似也没有这样的必要。

最后还发现了一篇一步到位的iOS响应者链的全过程:iOS触摸事件的流动(想有更清晰的了解的看)
直接引用里面的一张图:

image.png

参考资料:
响应者链及相关机制总结

相关文章

  • iOS 响应链

    iOS开发 - 事件传递响应链iOS 响应者链,事件的传递事件传递之响应链Cocoa Touch事件处理流程--响...

  • 二、事件传递链和响应者链

    iOS触摸事件详解iOS开发-事件传递响应链 响应者链 UIResponser包括了各种Touch message...

  • iOS响应者链

    参考好文 iOS开发-事件传递响应链,用运行时分析 iOS事件传递:响应者链[译] http://www.jian...

  • iOS UI事件传递与响应者链

    iOS UI事件传递与响应者链 响应者链 响应者对象:继承自UIResponder的对象称之为响应者对象。UIAp...

  • iOS-触摸事件传递、事件响应者链

    前言,本文简单了解触摸事件传递和事件响应者链。 一、知识点简介 1.1 iOS中的事件介绍 iOS中的事件可以分为...

  • iOS开发之触摸事件

    本文介绍了iOS中使用频率较高的触摸事件,并阐述了事件产生和传递的过程,以及响应者链的事件传递过程 触摸事件 简介...

  • iOS中事件处理机制——触摸、手势、控制

    响应者链 首先,想要理解事件的处理机制必须要知道iOS中响应者链,要明白事件是怎么传递的。 如上图,假设我们点击v...

  • iOS知识收集

    1. 响应者链 1.1 Cocoa Touch事件处理流程--响应者链 1.2 事件传递之响应链 多线程 http...

  • 响应者链

    什么是响应者链 在iOS的事件传递过程中需要通过多个响应者来寻找最合适的事件接收者,把这个过程中的响应者串联起来就...

  • iOS中手势的简述(面试必备)

    提供了有关iOS中手势常用到的6个方面(事件、事件处理的方法、UITouch触摸对象、事件传递、响应者链、手势识别...

网友评论

    本文标题:iOS响应者链、事件的传递

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