iOS上的事件传递:第2部分

在本系列的第一部分中,我们讨论了触摸处理。 现在,让我们更深入地研究“响应者链”的工作方式,并查看可在iOS应用程序中处理的其他类型的事件:运动事件,远程控制事件和按键命令。

有关响应者链的更多信息

我们在介绍触摸事件时简要概述了响应程序链,但让我们仔细看一下。 响应者链是在整个应用程序中传递这些其他类型事件的主要机制。

响应者链的概念很简单。 它由UIResponder对象链组成。 链中的每个元素都有机会响应给定的消息。 如果给定的响应者不响应该消息,则链中的下一个项目将有机会进行响应。 如果链中没有对象响应该消息,则该消息将被丢弃。

第一个有机会响应消息的对象称为“第一响应者”。第一响应者是最近调用-[UIResponder成为FirstResponder]的对象 。 通过覆盖-[UIResponder canBecomeFirstResponder]并返回YES,对象可以成为第一响应者。 当对象作为第一响应者完成时,调用-[UIResponder resignFirstResponder]

作为动作事件和远程控制事件的响应者,非常简单。 作为第一响应者的对象被发送了运动或远程控制消息。 然后,它就有机会响应事件,将事件发送到响应者链,或者通过处理事件然后在超类上调用方法来完成。 涉及的方法在UIResponder中定义。 Apple的UIResponder类参考是获取更多信息的重要来源。

在实践中

我们可能会问,“这一切都很好,但是我可以在实践中使用它吗?”让我们来看一个示例应用程序。 首先,请克隆GitHub存储库。

该应用程序不是很有用,但是它演示了响应程序链如何工作的简单示例。 它需要一个硬件键盘,并且只能做一件事:在灰色框中打印字母“ a”。 它可以在模拟器或带有键盘的任何iOS设备中正常运行。 让我们深入一些代码。

首先,让我们看一下主视图控制器:

 类ViewController:UIViewController { 
@IBOutlet弱var customTextView:CustomTextView!

@IBAction func textViewTapped(_ sender:AnyObject){
customTextView.becomeFirstResponder()
}
}

这个视图控制器很简单。 它包含CustomTextView类型的自定义视图,并处理在情节提要中配置的轻击手势识别器的操作。 轻手势处理程序在customTextView上调用beginFirstResponder 。 现在让我们看一下CustomTextView

 类CustomTextView:UIView { 
私人var textToRender:NSString吗? {
didSet {
setNeedsDisplay()
}
}

func render(text:NSString){
textToRender =文字
}

覆盖func draw(_ rect:CGRect){
super.draw(矩形)

textToRender?.draw(at:.zero,withAttributes:nil)
}

覆盖var canBecomeFirstResponder:布尔{
返回真
}

覆盖var canResignFirstResponder:布尔{
返回真
}

是否覆盖var keyCommands:[UIKeyCommand]? {
var array = [UIKeyCommand]()

让keyCommand = UIKeyCommand(input:“ a”,
ModifyFlags:[],
行动:
#selector(KeyCommandReceiver.aKeyPressed(sender :)))

array.append(keyCommand)
返回数组
}
}

这种观点有点复杂。 让我们从顶部开始。 首先,它定义了textToRender属性,并在设置时调用setNeedsDisplay() 。 有一个render(text 🙂方法,用于设置传递给textToRender的文本。 该视图覆盖draw(_ 🙂 ,后者只是在视图上绘制文本而没有任何属性。

下一部分是UIResponder进入的位置。该视图可以成为第一响应者,也可以退出其第一响应者状态。 然后定义一个keyCommands数组。 当前,它定义了一个按键命令。 它响应按下的“ a”键,并具有定义为#selector(KeyCommandReceiver.aKeyPressed)的动作。 KeyCommandReceiver是具有以下定义的协议:

  @objc协议KeyCommandReceiver:类{ 
func aKeyPressed(发送方:AnyObject?)
}

所有这些定义就是触发key命令时发送的消息。 触发键盘命令后,该命令将从第一个响应者开始发送到响应者链。 在我们的应用程序中,我们不希望视图处理事件,因此我们在视图控制器上实现该方法。

 扩展ViewController:KeyCommandReceiver { 
func aKeyPressed(sender:AnyObject?){
customTextView.render(text:“ a”)
}
}

这说明了响应链如何发挥作用。 根据文档, UIKeyCommand的操作从第一个响应程序开始,并遍历响应程序链,直到该操作得到处理为止。 如果未处理该操作,则它会静默失败。

最后说明

如果没有第一响应者会怎样? 系统将根据当前视图和视图控制器层次结构确定应采取的措施。 根据我的经验,如果没有第一响应者,则不能保证将事件传递给某个响应者。 例如,一个简单的视图控制器包含一个视图控制器,该视图控制器包含在另一个没有“兄弟”的视图控制器中,会形成一条从子视图控制器到其父视图的简单链。 如果您的父视图控制器包含多个子视图控制器,那么在系统找出响应程序链的起始位置时,将导致子视图被跳过。

如果要确定响应者链中的确定性路径,请通过调用beginFirstResponder来定义第一响应者。 您还可以通过覆盖nextResponder并返回链的下一部分来更改Responder链的顺序。 请小心执行此操作,因为您可能会完全中断响应程序链并导致某些不确定的行为。

包起来

当您需要为运动事件,远程控制事件或按键命令实现处理程序时,了解事件流至关重要。 在设计应用程序时,这些知识非常有用。

在第3部分中,我们将介绍用于处理用户交互的另一种模式。 我们还将讨论如何将响应程序链用于应用程序中的自定义事件。