ios事件传递响应链基础知识整理

ios事件传递响应链基础知识整理
一、ios事件传递响应链:
1、ios的事件:
触摸(touch)事件
运动事件
远程事件
二、事件的生命周期和响应链:
1、事件生命周期:
事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view、寻找最合适的view的底层实现、拦截事件的处理)->找到最合适的view后事件的处理(例如:touches方法的重写,也就是事件的响应)
2、响应链:
在ios中并不是所有对象都可以去处理事件。UIResponder:是所有响应事件类的基类,UIView和UIViewController都是作为响应事件的载体,当一个View被add到superView上时,它的nextResponder属性就会被指向它的superView,当controller初始化的时候,self.view(topmost view)的nextResponder属性会指向所在的controller,而controller的nextResponder会指向所在的controller,而controller的nextResponder会被指向self.view的superView,这样,整个app就通过nextResponder串成了一条链,也就是我们所说的响应链。通过UIResponder的属性串连起来的
3、UIResponder内部提供了以下方法来处理事件
触摸事件:之间都是UITouch对象
4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数。一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数。如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象。如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象。根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸。
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;//一根手指或者多根手指开始触摸时,系统会自动的调用
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;//一根手指或者多根手指移动时,系统会自动的调用(随着手指的移动,系统会进行持续的调用
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; //手指离开view时,系统会自动的调用
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; //触摸结束前,某个系统事件会打断触摸过程,从而系统调用这个方法
加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
4、UITouch的作用
1、保存着跟手指相关的信息。如:触摸的位置、时间、阶段
2、当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指的位置。
3、当手指离开屏幕时,系统会销毁相应的UITouch对象
UITouch的属性:
触摸产生时所处的窗口:
@propety(nonatomic,readonly,retain)UIWindow *window;
触摸产生时所处的视图
@propety(nonatomic,readonly,retain)UIView *view;
短时间内点击屏幕的次数,可以根据topCount判断点击、双击或者更多的点击
@propety(nonatomic,readonly)NSUInteger tapCount;
记录了触摸事件产生或者变化时的时间:
@propety(nonatopic,readonly)NSTimeInterval timestamp;
当触摸事件所处的状态:
@propety(nonatopic,readonly)UITouchPhase phase;
UITouch的方法:
- (CGPoint)locationInView:(UIView *)view;
返回值表示触摸在view上的位置 
这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0)) 
调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
- (CGPoint)previousLocationInView:(UIView *)view; //记录前一个触摸点的位置
UITouch的phase有什么用?
触摸事件在屏幕上有一个周期,即触摸开始、触摸点移动、触摸结束,还有中途取消。而通过phase可以查看当前触摸事件在一个周期中所处的状态。phase是UITouchPhase类型的,这是一个枚举配型,包含了UITouchPhaseBegan(触摸开始) UITouchPhaseMoved(接触点移动) UITouchPhaseStationary(接触点无移动) UITouchPhaseEnded(触摸结束) UITouchPhaseCancelled(触摸取消) 
5、UIEvent:每产生一个事件,就会产生一个UIEvent对象。称为事件对象,记录事件产生的时刻和类型
事件类型
@property(nonatomic,readonly) UIEventType type;
@property(nonatomic,readonly) UIEventSubtype subtype;
事件产生的时间
@property(nonatomic,readonly) NSTimeInterval timestamp;
UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)
6、UIView不接触触摸事件的几种情况
1)不接收用户交互 :userInteractionEnabled = NO;
2)隐藏:hidden = YES

3)透明:alpha = 0.0~0.01

4)没有实现touchesBagin:withEvent:方法,直接执行touchesMove:withEvent:等方法

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

三、事件的传递和产生
1)发生触摸事件后,系统将该事件加入到一个有UIApplication管理的事件队列中。
2)UIApplication会从事件队列中取出最前面的事件,并将事件分配下去,通常先把发送事件给应用程序的主窗口。
3)主窗口会在视图层次结构中找到一个最合适的视图俩处理触摸事件。
四、对于拦截事件的处理:
1、在遍历寻找最合适视图过程中,会调用视图的两个重要方法:
1)hitTest:withEvent:方法
1)pointInside方法
hit:withEvent:方法底层会调用pointInside:withEvent:方法判断点在不在方法调用者的坐标系上。
只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法,用于寻找并返回最合适的view(能够响应事件的那个最合适的view)。如果该方法返回nil,那么事件便不会往下遍历,也就是调用该方法的控件本身和其子控件都不是最合适的view,那么最合适的view就是该控件的父控件。如果返回的是view,不管该事件是点在哪的,都以该view为最合适视图。
2、拦截思路有两种:
1)想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件。
2)重写自己的hitTest:withEvent:方法 return self。 

但是,建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view。因为事件传递遍历控件的时候,子控件视图都是从后往前遍历的,也就是后添加的视图先检查,如果有多个子视图,就有可能还没遍历到你就先返回真正合适的view。
五、子视图不需要响应tap事件: 在子视图上面也添加一个Tap,然后手势的选择子不做任何处理
1、hittest方法的底层实现与应用:
1)hittest方法内部会调用pointInside方法,询问触摸点是不是在自己的身上,当遍历子控件时,传入的坐标点要进行转化(将父视图上的坐标点转换到要传递的子视图上的坐标点)。
2)hitTest的底层实现:当控件接收到触摸事件的时候,不管能不能处理事件,都会调用hitTest方法。
该方法的底层实现是:
  • 先看自己能否接受触摸事件
  • 再看触摸点是否在自己的身上
  • 从后向前遍历子控件,拿到子控件后,再次的重复1,2步骤,要把父控件上的坐标点转化成子坐标系下的点,再次执行hitTest方法。
3)如果最后没有找到合适的View,那么就return self,自己就是合适的view.
代码实现:
//什么时候调用:当一个事件传递给当前View,就会调用.
//返回值:返回的是谁,谁就是最适合的View(就会调用最适合的View的touch方法)
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //1.判断自己能否接收事件
    if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
        return nil;
    }
    //2.判断当前点在不在当前View.
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    //3.从后往前遍历自己的子控件.让子控件重复前两步操作,(把事件传递给,让子控件调用hitTest)
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        //取出每一个子控件
        UIView *chileV =  self.subviews[i];
        //把当前的点转换成子控件坐标系上的点.
        CGPoint childP = [self convertPoint:point toView:chileV];
        UIView *fitView = [chileV hitTest:childP withEvent:event];
        //判断有没有找到最适合的View
        if(fitView){
            return fitView;
        }
    }
    //4.没有找到比它自己更适合的View.那么它自己就是最适合的View
    return self;    
}
总结:事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view。所以是,先传递事件,再根据事件在自己身上找更合适的view。不管子控件是不是最合适的view,系统默认都要先把事件传递给子控件,经过子控件调用自己的hitTest:withEvent:方法验证后才知道有没有更合适的view。即便父控件是最合适的view了,子控件的hitTest:withEvent:方法还是会调用,不然怎么知道有没有更合适的!即,如果确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。
注:如果直接使用系统的button那么会有特效产生,如果不想要特效,将类型选择为costom。

猜你喜欢

转载自blog.csdn.net/minld/article/details/79957356