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

Posted

技术标签:

【中文标题】当中间 viewController 包含两个 viewController 时 UIKeyCommands 不起作用【英文标题】:UIKeyCommands don't work when intermediary viewController contains two viewControllers 【发布时间】:2016-08-29 22:18:28 【问题描述】:

我相信这是一个与 UIKeyCommands、ViewController 和/或响应者的层次结构相关的重要问题。

在我的 ios 9.2 应用程序中,我有一个名为 NiceViewController 的类,它定义了 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 作为唯一的孩子添加到我的主视图控制器时,一切正常 - 按下外部键盘上的按钮“1”(在模拟器中使用时为物理键盘)就像一个魅力。但是,当我向主视图控制器添加第二个视图控制器时,NiceViewController 中定义的 UIKeyCommands 停止工作。

我很想了解为什么会发生这种情况以及如何确保将多个子视图控制器连接到我的主视图控制器不会阻止这些子视图控制器处理 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)

  
    

【问题讨论】:

切换添加子视图控制器的顺序会改变观察到的结果吗? @LeonidUsov 我尝试了两个命令,结果完全一样。 【参考方案1】:

我不认为这是UIKeyCommands 的问题

在 iOS 中,一次只有一个 View Controller 可以管理键盘命令。因此,通过您的设置,您将拥有一个带有几个子视图控制器的容器视图控制器。您应该告诉 iOS,您希望 NiceViewController 控制键盘命令。

定义第一响应者

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

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

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

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

请参阅UIKeyCommand 的文档:

系统总是最先有机会处理关键命令。映射到已知系统事件(如剪切、复制和粘贴)的键命令会自动路由到适当的响应器方法。对于其他按键命令,UIKit 会在响应者链中查找具有与按下的键匹配的按键命令对象的对象。如果它找到这样一个对象,它就会遍历响应者链,寻找第一个实现相应操作方法的对象,并调用它找到的第一个对象。

注意:当有人与其他 VC 交互时是第一响应者NiceViewController 不能同时是第一响应者,所以你可能也需要其他 VC 上的一些关键命令。

为什么这并不总是必要的

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

【讨论】:

为什么它可以在我的示例中工作(当只有一个子视图控制器时)而不必覆盖canBecomeFirstResponder 我的猜测是它能够假设由于只有一个 VC,它是第一响应者。但是,当您有一个带有一些子级的容器视图控制器时,它假定容器本身是第一响应者而不是子级。 这看起来是一个可靠的答案。查看链中哪个响应者是第一响应者的一种方法是在运行时使用 [[UIWindow keyWindow] firstResponder] 并查看发生了什么。这是一个私有 API,因此仅在调试时使用。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

我不确定为什么它的工作原理与您在任何时候都应该只有一个 firstResponder 完全一样。

【讨论】:

以上是关于当中间 viewController 包含两个 viewController 时 UIKeyCommands 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

关于图中两个顶点间的路径问题

如何创建一个透明的 ViewController? [复制]

iPhone - 如何制作 viewControllers 风景?

cqyz oj | 树的分治 | 树形DP | 树的重心

用Dijkstra算法求图中从顶点a到其他各顶点间的最短路径,并写出执行算法过程中各步的状态。

通过 iCarousel 将信息传递给多个 viewController