iOS中的事件
事件的生命周期
事件的生命周期从事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view、寻找最合适的view的底层实现、拦截事件的处理)->事件的响应(找到最合适的view后对事件进行处理,touches方法的重写)
事件的分类
iOS中的事件可以分为3大类型:
触摸事件
加速计事件
远程控制事件
响应者对象
在iOS中只有继承了UIResponder的对象才能接收和处理事件,UIResponder的子类包括UIView,UIViewController,UIApplication等。响应者接收到原始事件数据,必须处理事件转发到另一个响应者对象,当APP接收到一个事件事,UIKit自动引导事件到最合适的的响应者对象,也叫做第一响应者。
UIResponder内部提供了以下方法来处理事件
触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (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;
UITouch对象
UITouch对象的产生
当用户触摸屏幕的时候,会创建一个与手指相关的UITouch对象,一根手指对应一个UITouch对象,如果两根手指同时触摸,则touchesBegan的方法中参数touches有两个UITouch对象。
UITouch对象的作用
每一个UITouch对象都记录保存着这次触摸的相关信息,包括位置,时间,和触摸的阶段。当手指移动时,会更新这个对象的一些属性,当手指离开屏幕的时候系统辉销毁相应的UITouch对象
UITouch对象的属性
触摸产生时所处的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
触摸产生时所处的视图
@property(nonatomic,readonly,retain) UIView *view
;
短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;
记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;
当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;
UITouch的方法。
(CGPoint)locationInView:(UIView *)view;
// 返回值表示触摸在view上的位置
// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
// 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
(CGPoint)previousLocationInView:(UIView *)view;
// 该方法记录了前一个触摸点的位置
这两个方法则实现了对UITouch对象的一个更新。
iOS事件的产生和传递
事件的产生
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,因为队列的特点是先进先出,先产生的事件先处理,这样符合常理。
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去,通常会发送给应用程序的主窗口,按照视图层级结构一级级向下寻找对合适的视图来处理事件。
找到合适的视图后,会调用touches的方法来做具体的处理,是处理这个事件还是往上传递交给上一级处理。
事件的传递
触摸事件的传递是从上到下的传递,由父控件传递给子控件。UIApplication->window->寻找处理事件最合适的view
如果父控件不能接受触摸事件,那么子控件也就不可能接收到触摸事件。
- 首先判断主窗口(key window)自己能否接收这个触摸事件
- 判断触摸点是否在自己身上
- 遍历当前视图的子控件数组,从后往前遍历子控件数组再重复前面两个步骤(先查找子控件数组中的最后一个元素,再执行1,2两个步骤)。
- 假设遍历过后找到了一个较为合适的view,则把这个事件交给这个view,再对这个view进行遍历他的子控件数组,像前三步一样继续寻找更合适的view,直到找不到更合适的view。
- 如果找不到符合条件的子控件,则认为自己是最适合处理这个事件的控件,也就是自己是最合适的view。
UIView不能接收事件的三种情况
- 不允许交互:userInteractionEnabled = NO
- 隐藏:hidden = yes 父控件被隐藏后,子控件也跟着杯隐藏,隐藏的控件不能接收事件
- 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明
默认UIImageView不能接收触摸事件,如果希望UIImageView可以交互,需要设置UIImageView的userInteractionEnabled = YES。
总结
- 产生一个触摸事件,由UIApplication添加到事件队列中。
- UIApplication取出最前面的事件把事件传递给主窗口(keyWindow)
- 根据视图层级一级级寻找最合适的处理事件的view。
最核心的点就是如何寻找最合适的view
寻找最合适的view原则就只有两个。一个是判断当前控件是否能够处理事件,第二是当前的触摸点是否在自己身上。符合这两个条件,则事件就会传递给当前控件。再由当前控件作为父控件进行遍历,寻找最合适的子控件。依次逐级寻找,当再也找不到更合适的子控件时,则当前的父控件为最合适的view。
实现这两个条件的判断则需要用到最关键的两个方法
hitTest:withEvent:方法
pointInside方法
hit:withEvent:方法底层会调用pointInside:withEvent:方法判断点在不在方法调用者的坐标系上。
hitTest:withEvent:方法
只要事件传递给一个控件,则必然会调用这个控件的hitTest:withEvent:方法,这个方法会返回寻找到的最合适的view。也可能是一个view,则view是更为合适的,继续将事件传递给该view,也啃呢个返回一个nil,则代表该控件和该控件下的子控件都不是最合适的view,则最合适的view是该控件的父控件。
事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view。所以是,先传递事件,再根据事件在自己身上找更合适的view。
不管子控件是不是最合适的view,系统默认都要先把事件传递给子控件,经过子控件调用子控件自己的hitTest:withEvent:方法验证后才知道有没有更合适的view。即便父控件是最合适的view了,子控件的hitTest:withEvent:方法还是会调用,不然怎么知道有没有更合适的!即,如果确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。
技巧:想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件,或者重写自己的hitTest:withEvent:方法 return self。但是,建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!
特殊情况:
谁都不能处理事件,窗口也不能处理。
重写window的hitTest:withEvent:方法return nil
只能有窗口处理事件。
控制器的view的hitTest:withEvent:方法return nil或者window的hitTest:withEvent:方法return self
return nil的含义:
hitTest:withEvent:中return nil的意思是调用当前hitTest:withEvent:方法的view不是合适的view,子控件也不是合适的view。如果同级的兄弟控件也没有合适的view,那么最合适的view就是父控件。
事件的响应
触摸事件处理的整个过程
当通过事件传递,找到最合适的view之后,则进行事件的响应。事件的传递是由上到下的寻找最合适的view,事件的响应则是由下往上去找到最合适的响应者去进行处理。
- 如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
- 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
- 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
- 如果UIApplication也不能处理该事件或消息,则将其丢弃
总结
- 触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)
- UIApplication会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口(keyWindow)
- 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)
- 最合适的view会调用自己的touches方法处理事件
- touches默认做法是把事件顺着响应者链条向上抛。直到找到实现了touches方法的view,则把事件处理掉。否则一直向上一级响应者传递,直到事件处理或者最后也无法处理则丢弃。
如何做到一个事件多个对象处理:
因为系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 1.自己先处理事件...
NSLog(@"do somthing...");
// 2.再调用系统的默认做法,再把事件交给上一个响应者处理
[super touchesBegan:touches withEvent:event];
}
事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
网友评论