CS193P第十四堂课摘要及心得笔记

在这一次的课堂上,我们将学习在 iPhone 开发平台上关于触控事件的处理,而多点触控的功能更是 iPhone 应用程式开发的重点,就让我们一起来看看吧!

触控的序列

在 iPhone 的操作上面,像是如下图般一笔划的触控动作,会被转成一系列的 UITouch 物件,储存在 UIEvent 之后传递给 view。

CS193P第十四堂课摘要及心得笔记

UITouch 物件描述了某支手指头在触控萤幕上得动作,包含以下这些属性:

@property NSTimeIntervaltimestamp; // 触控的时间点 @property UITouchPhasephase; // 触控中的哪一阶段 @property NSUIntegertapCount; // 连续点了几下 @property UIWindow*window; // 在哪个 Window 上点击 @property UIView*view; // 在哪个 View 上点击 - locationInView:view; // 在某个 view 上的座标位置 - previousLocationInView:view; // 在某个 view 上的前一个座标位置

而 UIEvent 如同我们刚刚所提及的,是 UITouch 的容器,当我们有多点触控的时候,这个容器就会包含数个 UITouch 物件。UIEvent 具有以下类别:

@property NSTimeInterval timestamp; // 触控的时间点 - allTouches; // 所有的 UITouch 物件 - touchesForWindow:window; // 回传某个 windows 上的 UITouch 物件 - touchesForView:view; // 回传某个 view 上的 UITouch 物件

而以上这些物件均会传透过 UIResponder 的方法传递给我们的应用程式,包含以下几个方法:

- touchesBegan:touches withEvent:event; // 触控开始 - touchesMoved:touches withEvent:event; // 触控点移动中 - touchesEnded:touches withEvent:event; // 触控结束 - touchesCancelled:touches withEvent:event; // 触控取消

上面所谓的触控取消,虽然仅会发生在很少的机会,像是忽然接到来电,但仍然不能忘记对这些物件进行处理,详细的原因在课堂的稍后会在做解释。

多点触控

在我们要开始接收包含多个 UITouch 的 UIEvent 物件之前,我们必须先将 UIView 的 BOOL multipleTouchEnabled; 属性设定成 YES 才行。

而当使用者若同时将两只手指放在 iPhone 上时、或者是两只手指头同时在萤幕上移动时,我们就会接收到包含多个 UITouch 物件的 UIEvent。但需要注意的是,如果使用者将两只手指同时放在画面上,但只有一个手指在移动,这样我们所接收到的 UIEvent 物件只会包含在移动中的 UITouch 物件!

CS193P第十四堂课摘要及心得笔记

而若是两只手指分别在不同的 View 上呢?那两个 View 分别会接收到 touchesMoved: withEvent: 的呼叫,并且除了自己 view 上的 UITouch 物件之外,也会收到另外一个 View 上得 UITouch 物件。

CS193P第十四堂课摘要及心得笔记

然而,有时候我们不希望使用者可以同时操作两个 UIView 物件,特别是我们在开发游戏的时候,我们可能会禁止使用者同时移动并且开火,这时候我们就可以设定 UIVIew 中的 BOOL exclusiveTouch; 属性为 YES,这样一来就可以避免同时有两个以上的 View 接收到 UIEvent 物件了。

或许当读者看到上面的方法会感到好奇,那 iPhone 是如何判断某个 Touch 是属于哪个 View 呢?事实上,iPhone 会透过呼叫 hitTest:withEvent: 这个测试方法,测试 UIWindow 上的每一个 view 是否符合以下三个条件:

当某个 UIView 符合以上条件的话,iPhone 就会继续测试其底下所包含的 subviews,直到找到最适合的 view 为止。

CS193P第十四堂课摘要及心得笔记

而当找到 View 之后,会对其 UIViewController 呼叫 touchesBegan:touches withEvent:event; 等方法传递 UIEvent 物件。但若是最底层的 View 没有实做这个方法回应触控事件的话,则会往 superview 传递 UIEvent 物件。然后重複这个循环,直到找到可以回应触控事件的 View 为止,而若是所有的 superview 对于目前的 UIEvent 物件没有处理的话,则会转而呼叫 UIWindow 甚至是 UIApplication 进行处理。整体的阶层架构如下图:

CS193P第十四堂课摘要及心得笔记
最佳作法

iPhone 开发平台释出也有一段时间了,随着越来越多的应用程式开发完成,开发者们也对于触控事件有一些常见的错误及解决方法,以下就是几个常见的开发好习惯:

当某个 View 被 - hitTest:point withEvent:event; 方法回传,也就代表他应该要处理所有的触控事件,包含以下四个:

- touchesBegan:touches withEvent:event; - touchesMoved:touches withEvent:event; - touchesEnded:touches withEvent:event; - touchesCancelled:touches withEvent:event;

由于大多数的触控处理都会暂时将 UIEvent 储存起来以跟后续的触控行为进行比对、计算,而如果我们漏掉处理其中某一种,就很有可能造成物件产生无法预期的错误。

而当我们需要继承 UIView 建立自己的 View 类别时,也必须遵守以上的惯例,也就是处理每一个阶段的触控事件。而且要注意,在这些处理方法中千万不能向上呼叫父类别的处理方法。

至于若是继承其他 UIKit 的类别,像是 UIButton 时,你可以选择处理某些触控事件即可,但是跟上面相反的是,你必须在每个触控事件处理方法呼叫父类别的相对方法,否则物件原有的触控反应将会无效。

最后,如果你万不得已,非得需要将某个触控事件转移给别的 View 的话,那要记得那些 View 必须是自己设计的 UIView 子类别,因为 UIKit 内建的类别并不保证可以在这样的状况下正确运作。

结论

相信各位读者应该在这次的课程中,对于触控的事件处理有很详细的了解,也已经可以透过这些知识设计出多点触控的应用程式了!在下一次的课程中,我们将探讨那些在 iPhone SDK 中所附的模拟器中无法测试的硬体功能,像是罗盘、GPS 或者是加速度计等等,请继续锁定本连载!

参考资源