当中介 viewController 包含两个 viewController 时,UIKeyCommands 不起作用

UIKeyCommands don't work when intermediary viewController contains two viewControllers

我认为这是一个 non-trivial 问题,与 UIKeyCommands、ViewControllers and/or 响应者的层次结构有关。

在我的 iOS 9.2 应用程序中,我有一个名为 NiceViewController 的 class,它定义了 UIKeyCommand,它会导致向控制台打印一些内容。

这里是NiceViewController

class NiceViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    let command = UIKeyCommand(input: "1", modifierFlags:UIKeyModifierFlags(), 
                   action: #selector(keyPressed), discoverabilityTitle: "nice")

    addKeyCommand(command)
  }

  func keyPressed() {
    print("works")
  }
}

当我将 NiceViewController 作为唯一的 child 添加到我的主视图控制器时,一切正常 - 按下外部键盘上的按钮“1”(在模拟器中使用时的物理键盘)就像魅力。但是,当我将第二个视图控制器添加到我的主视图控制器时,NiceViewController 中定义的 UIKeyCommands 停止工作。

我很想知道为什么会这样,以及如何确保我的主视图控制器有多个 child 视图控制器不会阻止那些 child 视图控制器处理 UIKeyCommands.

这是我的主视图控制器:

class MainViewController: UIViewController {
  let niceViewController = NiceViewController()
  let normalViewController = UIViewController()

  override func viewDidLoad() {
    super.viewDidLoad()


    self.view.addSubview(niceViewController.view)
    self.addChildViewController(niceViewController)

    self.view.addSubview(normalViewController.view)
    // removing below line makes niceViewController accept key commands - why and how to fix it?
    self.addChildViewController(normalViewController)

  }
}    

我不认为这是 UIKeyCommands

的问题

在iOS中,一次只能有一个View Controller管理按键命令。因此,通过您的设置,您有一个容器视图控制器和几个 child 视图控制器。您应该告诉 iOS 您希望 NiceViewController 控制按键命令。

定义急救人员

在高层次上,为了支持键盘命令,您不仅必须创建一个 UIKeyCommand 并将其添加到视图控制器中,而且还必须使您的视图控制器成为第一响应者,以便它能够响应键盘命令。

首先,在您想要使用键盘命令的任何视图控制器中,您应该让 iOS 知道该控制器能够成为第一响应者:

override func canBecomeFirstResponder() -> Bool {
    // some conditional logic if you wish
    return true
}

接下来,您需要确保 VC 确实成为第一响应者。如果任何 VC 包含某种成为响应者(或类似的东西)的文本字段,那么 VC 可能会自己成为第一响应者,但您始终可以调用 becomeFirstResponder() NiceViewController 使其成为第一响应者,除其他外,响应关键命令。

请参阅 UIKeyCommand 的文档:

The system always has the first opportunity to handle key commands. Key commands that map to known system events (such as cut, copy and paste) are automatically routed to the appropriate responder methods. For other key commands, UIKit looks for an object in the responder chain with a key command object that matches the pressed keys. If it finds such an object, it then walks the responder chain looking for the first object that implements the corresponding action method and calls the first one it finds.

注意:当某人与另一个人互动时VC并且是第一响应者,NiceViewController 不能同时成为第一响应者,因此您可能还想在另一个 VC 上执行一些关键命令。

为什么这并不总是必要的

当只有一个VC出现时,iOS似乎假定它将是第一响应者,但是当你有一个容器时VC,iOS似乎将容器视为第一响应者,除非有 child 表明它能够成为第一响应者。

按照@Matthew 的解释解决方案是添加becomeFirstResponder() 请求;在 viewDidAppear 而不是 viewDidLoad 中解决了我的类似问题。 Swift4

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    becomeFirstResponder()
    print("becomeFirstResponder?: \(isFirstResponder)")
}

我在试验时发现,如果您在子视图控制器上手动调用 becomesFirstResponder(),它允许您有多个第一响应者,并且在按下命令时显示所有关键命令。

我不确定为什么这会像您在任何时候都应该只有一个 firstResponder 一样有效。