首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》11.7 响应器链

关灯直达底部

响应器是个知道如何直接接收UIEvent的对象(参见11.6节内容)。它之所以知道是因为它是UIResponder或UIResponder子类的实例。查看Cocoa类的继承体系,你会发现与屏幕显示相关的任何类都是个响应器。UIView是个响应器、UIWindow是个响应器、UIViewController是个响应器,甚至连UIApplication与应用委托也是个响应器。

UIResponder有4个底层方法来接收与触摸相关的UIEvent:

·touchesBegan:withEvent:

·touchesMoved:withEvent:

·touchesEnded:withEvent:

·touchesCancelled:withEvent:

这些方法(touch方法)会被调用以通知某个触摸事件的响应器。无论代码最终以何种方式获悉某个用户相关的触摸事件,实际上,即使代码并不知晓某个触摸事件(因为Cocoa以某种自动化的方式对触摸进行响应而无需你的代码介入),该触摸最初也会通过这4个方法中的其中一个来告知响应器。

该通信机制首先会确定用户触摸了哪个响应器。当找到了正确的视图后(单击测试视图),UIView的方法hitTest:withEvent:与pointInside:withEvent:就会得到调用。然后会调用UIApplication的sendEvent:方法,它又会调用UIWindow的sendEvent:,而它又会调用点击测试视图(响应器)正确的触摸方法。

应用中的响应器会加入响应器链中,响应器链本质上会沿着视图层次体系将响应器链接起来。一个UIView可能位于另一个UIView中(其父视图)等,直到到达了应用的UIWindow(它没有父视图)。响应器链从下向上的样子如下所示:

1.起始的UIView(这里指的就是单击测试视图)。

2.如果该UIView是UIViewController的视图,那么就是该UIViewController。

3.UIView的父视图。

4.重复第2步!不断重复,直到到达......

5.UIWindow。

6.UIApplication。

7.UIApplication的委托。

11.7.1 推迟职责

我们可以使用响应器链推迟一个响应器对某个触摸事件的处理。如果响应器接收到某个触摸事件但却不能对其进行处理,那么该事件就会沿着响应器链向上查找能够处理的响应器。这主要发生在如下两种情况中:

·响应器没有实现相关的触摸方法。

·响应器实现了相关的触摸方法,调用的是super。

比如,基本的UIView本身并没有实现触摸方法。这样,在默认情况下,虽然UIView是个单击测试视图,但触摸事件却无法进入UIView中,它会沿着响应器链向上查找能够对其响应的响应器。在某些情况下,将对这种触摸的处理推迟到主背景视图,甚至是控制它的UIViewController中是合情合理的。

下面要介绍的这个应用是我开发的。它是个简单的拼图游戏:一个矩形图片被划分为多个小块,并且被打乱了。用户需要轻拍连续的两个小块来交换它们的位置。其背景视图是个名为Board的UIView子类;每个小块都是普通的UIView对象,并且是Board的子类。当用户轻拍Board时,我们需要知道哪个块会做出响应以及拼图的总体布局,不需要每一小块包含任何轻拍检测逻辑。因此,我利用响应器链来推迟职责:每一小块都没有实现任何触摸方法,对每一小块的轻拍会直接落到Board上,它会进行触摸检测并处理轻拍事件,并且告诉被轻拍的小块应该做什么。当然,用户对此一无所知;从表面上看,你触摸的是每一个小块,并且由它作出了响应。

11.7.2  Nil-Targeted动作

所谓nil-targeted动作就是个目标-动作对,其中的目标为nil。由于并没有指定目标动作,因此使用下面的规则:从单击测试视图(用户与之交互的视图)开始,Cocoa会沿着响应器链向上查找(一次查找一个响应器)能够响应该动作消息的对象:

·如果找到了能够处理该消息的响应器,那就会调用该响应器的方法,流程结束。

·如果一直到响应器链的顶部也找不到能够处理该消息的响应器,那么消息就不会得到处理(也没有任何副作用),换言之,什么都不会发生。

假设我们要通过代码来配置一个按钮,如下所示:


self.button.addTarget(nil,    action: /"buttonPressed:/",    forControlEvents: .TouchUpInside)  

这是个nil-targeted动作。当用户轻拍按钮时会发生什么事情呢?首先,Cocoa会查看UIButton本身,看看它能否响应buttonPressed:。如果不能,那么它会查找其父视图UIView,诸如此类,一直沿着响应器链向上查找。对于包含了按钮的视图来说,如果self是包含了这个视图的视图控制器,并且该视图控制器所对应的类实现了buttonPressed:,那么轻拍按钮就会导致视图控制器的buttonPressed:被调用!

通过代码来构建nil-targeted动作是显而易见的事情:创建一个目标-动作对,其中目标为nil,就像上一个示例那样。不过,如何在nib中构建nil-targeted动作呢?答案就是:构造一个对First Responder代理对象的连接。这正是First Responder代理对象的作用!First Responder并不是某个已知类的真实对象,因此在将动作连接到它之前,你需要在First Responder代理对象中定义动作消息,如下所示:

1.选中nib中的First Responder代理,并切换至属性查看器。

2.你会看到一个用户定义的nil-targeted First Responder动作表格(可能为空)。单击+按钮,为新动作指定一个签名;它必须接收一个参数(这样其名字会以一个冒号结尾)。

3.现在可以按住Control键并将控件(如UIButton)拖曳到First Responder代理上使用指定的签名来定义一个nil-targeted动作。