iOS响应链:UIResponder,UIEvent,UIControl和使用

在上一个操作由UIViewController处理的示例中,UIKit首先将操作发送给了UIView第一响应者-但由于它没有实现myCustomMethod因此视图将操作转发给了下一个响应者–恰好具有该方法的UIViewController在执行中。

尽管在大多数情况下,“响应者链”只是子视图的顺序,但您可以对其进行自定义以更改一般流程顺序。 除了能够覆盖next属性以返回其他内容之外,您还可以通过调用becomeFirstResponder()强制UIResponder成为第一响应者,并通过调用becomeFirstResponder()使UIResponder返回其位置。 这通常与UITextField结合使用以显示键盘inputView可以定义一个可选的inputView属性,该属性仅在响应者是第一响应者(在这种情况下为键盘)时显示。

响应者链自定义用途

尽管Responder Chain由UIKit完全处理,但您可以根据自己的喜好使用它来解决通信/代理问题。

在某种程度上,您可以将UIResponder操作视为一次性通知。 考虑一个几乎每个视图都支持“闪烁”动作的应用程序,目的是帮助用户在教程中导航。 触发此操作后,如何确保仅当前“活动”视图闪烁? 可能的解决方案包括使每个视图都继承一个委托或使用除"currentActiveView"之外每个人都需要忽略的简单通知,但是响应程序操作使您可以使用零委托和最少的编码轻松实现此目的:

 final class BlinkableView: UIView { 
override var canBecomeFirstResponder: Bool {
return true
}

func select() {
becomeFirstResponder()
}

@objc func performBlinkAction() {
//Blinking animation
}
}

UIApplication.shared.sendAction(#selector(BlinkableView.performBlinkAction), to: nil, from: nil, for: nil)
//Will precisely blink the last BlinkableView that had select() called.

它的工作原理与常规通知非常相似,不同之处在于,尽管通知将触发注册它们的每个人,但是这会有效地循环响应程序链,并在找到第一个BlinkableView时立即停止。

如前所述,甚至可以基于此构建体系结构。 以下是协调器结构的框架,该结构定义了事件的自定义类型并将其自身注入到响应者链中:

 final class PushScreenEvent: UIEvent { 

let viewController: CoordenableViewController

override var type: UIEvent.EventType {
return .touches
}

init(viewController: CoordenableViewController) {
self.viewController = viewController
}
}

final class Coordinator: UIResponder {

weak var viewController: CoordenableViewController?

override var next: UIResponder? {
return viewController?.originalNextResponder
}

@objc func pushNewScreen(sender: Any?, event: PushScreenEvent) {
let new = event.viewController
viewController?.navigationController?.pushViewController(new, animated: true)
}
}

class CoordenableViewController: UIViewController {

override var canBecomeFirstResponder: Bool {
return true
}

private(set) var coordinator: Coordinator?
private(set) var originalNextResponder: UIResponder?

override var next: UIResponder? {
return coordinator ?? super.next
}

override func viewDidAppear(_ animated: Bool) {
//Fill info at viewDidAppear to make sure UIKit
//has configured this view's next responder.
super.viewDidAppear(animated)
guard coordinator == nil else {
return
}
originalNextResponder = next
coordinator = Coordinator()
coordinator?.viewController = self
}
}

final class MyViewController: CoordenableViewController {
//...
}

//From anywhere in the app:

let newVC = NewViewController()
UIApplication.shared.push(vc: newVC)

这种工作方式是,每个CoordenableViewController保留对其原始下一个响应者(窗口)的引用,但是覆盖next而不是指向Coordinator ,后者又将窗口指向其下一个响应者。

 // MyView -> MyViewController -> **Coordinator** -> UIWindow -> UIApplication -> AppDelegate 

这允许Coordinator器接收系统事件,并且通过定义一个包含有关新视图控制器信息的新PushScreenEvent ,我们可以调度由这些Coordinators器处理的pushNewScreen操作来推送新屏幕。

有了这种结构,由于有响应程序链,UIKit可以确保仅向当前Coordinator器通知此操作,因此可以从应用程序中的任何位置调用UIApplication.shared.push(vc: newVC) ,而无需单个委托或单例。 。

此处显示的示例具有很高的理论意义,但是我希望这可以帮助您了解响应程序链的目的和用途。

在我的Twitter上关注我-@rockthebruno,让我知道您想分享的任何建议和更正。

参考文献和优秀读物

使用响应者和响应者链来处理事件
UIResponder
UIEvent
UIControl