1.简介
在UIResponder简介文章中我们介绍了UIResponder可以处理的事件以及他的派生类。
本章中我们将介绍如何去覆盖UIView的touch方法来创建一个划线的小应用。
https://github.com/xufeng79x/XFTouchDemo
2.API介绍
1.一根手指或者多根手指触摸屏幕【有多少手指触摸了屏幕则会有多少UITouch对象在集合中】
- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
2.一根手指或者多跟手指持续的在屏幕上滑动(随着滑动的持续,这个方法也会持续的被调用)【有多少手指在屏幕上移动则会有多少UITouch对象在集合中】
- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
3.一根手指或者多跟手指离开屏幕【有多少手指离开屏幕则会有多少UITouch对象在集合中】
- (void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
4.当触摸正常结束前,事件被打断的情况下(电话等接入的情况等)【有多少手指还在屏幕上则会有多少UITouch对象在集合中】
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
在此例子中我们集成UIView,然后通过覆盖它的这些方法来取得控制权:
@interface XFDrowView : UIView @end
需要注意的是:
1.任何UIResponder的派生类中要么覆盖全部方法,要么不要去覆盖任何方法。
关于这一点的疑问是如果我对某个事件并不感兴趣,我并不想去处理怎么办,需要在派生类中调用父类方法,千万不要去直接向nextResponder发送消息。
- (void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; }
这里涉及到响应者链知识,我们知道UIResponder中有一个属性叫做nextResponder,它记录着当前view的父view,如果我们不想处理某个事件那么我们就直接使用super让
父类方法去处理,最终这个事件将会被传递当顶层的UIResponder父类方法中,在它的方法中将会向 nextResponder 发送消息。我想在UIResponder类中,它的实现应该是这样的(猜测的,看都不到源码)
{
[nextResponder touchesEnded:touches withEvent:event] }
2.不要去保存UITouch对象
当手指按下后,当前手指对应的UITouch对象实例将永不改变,随着手指的移动,UITouch对象的属性值将会被刷新。
当手指离开屏幕后,UITouch对象将会被销毁,整个一个过程都是系统控制的,在外部不需要去存储对象,更何况它的属性值是被系统不断刷新的。
3. touch事件与UIControl
UIControl是UIButton、UISlider等控件的父类,它的顶层基类也是UIResponder类,而UIControl类直接覆盖了touch方法,让所有集成自UIControl的类可以共享代码。同样我们也没有办法获取UIControl的源代码,都是猜测:(
这里我们以UIButton控件为例子,在为UIButton对象注册目标对象和目标方法的时候需要指定控件事件类型,换句话说需要指明怎样“摸”它才能相应目标对象的目标方法,如下代码:
[self.myButton addTarget:self action:@selector(touchme) forControlEvents:UIControlEventTouchUpInside];
上述代码为某一个UIButton对象设置了当UIControlEventTouchUpInside事件类型的时候才去触发事件处理方法。
可以推测UIControl内部有可能是以下面的逻辑来覆盖touchesEnded方法的:
// 触摸结束 - (void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 指针指向已经结束的触摸对象 UITouch *touch = [touches anyObject]; // 触摸结束时的位置(使用当前UIControl对象的坐标系) CGPoint touchLocation = [touch locationInView:self]; // 判断结束时候是否在控件的bonds范围内 if (CGRectContainsPoint(self.bounds, touchLocation)) { // 向所有注册了UIControlEventTouchUpInside事件的对象发送动作消息 [self sendActionsForControlEvents: UIControlEventTouchUpInside]; }else{ // 触摸事件发生在bounds区域外,则向所有注册了UIControlEventTouchUpOutside事件的所有对象发送动作消息 [self sendActionsForControlEvents: UIControlEventTouchOutInside]; } }
可以看到UIControl为我们封装好了方法,对外只暴露了事件类型供开发者使用,简单方便。
4.touch目标确定
我们在屏幕上点击某个控件的时候,第一落点便已经确定了目标控件,当我们移动手指离开第一落点控件,或者在第一落点控件外抬起手指,它的事件接受对象都是第一落点控件。
那么系统是如何确定第一落点控件的呢?查看API文档:https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/event_delivery_responder_chain/event_delivery_responder_chain.html#//apple_ref/doc/uid/TP40009541-CH4-SW1
在这里可以简单的做一下“翻译”工作,加深机制的了解。
1.任何事件(touch事件,motion事件,remote事件等)发生后,都进入当前活跃Application对象的事件队列中
2.Application对象将队列中的事件按顺序拿出后分发给Window对象,window对象会去寻找首个可能会处理该事件的对象,而该对象的寻找方式根据事件的不同
而不同:
2.1如果是触摸事件,则将首个可能处理该事件的对象称作hit-test view,寻找他的过程称之为hit-testing。见hit-testing机制介绍
2.2如果是motion或者remote事件,则将首个可能处理该事件的对象称作first responder,first responder作为响应链的首个响应对象。见响应链机制介绍