UIKeyCommands не работают, когда промежуточный viewController содержит два viewController'а

Я считаю, что это нетривиальная проблема, связанная с UIKeyCommands, иерархией ViewControllers и/или респондентов.

В моем приложении 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» на внешней клавиатуре (физическая клавиатура при использовании в симуляторе) работает как шарм. Однако, когда я добавляю второй контроллер представления к моему основному контроллеру представления, UIKeyCommands, определенный в NiceViewController, перестает работать.

Я хотел бы понять, почему это происходит и как убедиться, что наличие нескольких дочерних контроллеров представления для моего основного контроллера представления не мешает этим дочерним контроллерам представления обрабатывать 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)

  }
}    

person mgamer    schedule 29.08.2016    source источник
comment
Меняет ли изменение порядка добавления контроллеров дочерних представлений наблюдаемые результаты?   -  person Leonid Usov    schedule 30.08.2016
comment
@LeonidUsov Я попробовал оба заказа, и результат был точно таким же.   -  person mgamer    schedule 30.08.2016


Ответы (3)


Я не думаю, что это проблема с UIKeyCommands

В iOS только один View Controller одновременно может управлять ключевыми командами. Итак, с вашей настройкой у вас есть контейнерный контроллер представления с парой дочерних контроллеров представления. Вы должны сообщить iOS, что хотите, чтобы NiceViewController имел контроль над ключевыми командами.

Определение первых респондентов

На высоком уровне, чтобы поддерживать ключевые команды, вы должны не только создать UIKeyCommand и добавить его в контроллер представления, но вы также должны разрешить своему контроллеру представления стать первым ответчиком, чтобы он мог реагировать на клавишу команды.

Во-первых, в любом контроллере представления, для которого вы хотите использовать ключевые команды, вы должны сообщить iOS, что этот контроллер может стать первым ответчиком:

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

Затем вам нужно убедиться, что венчурный капитал действительно стал первым ответчиком. Если какие-либо VC содержат какие-то текстовые поля, которые становятся респондентами (или что-то подобное), этот VC, вероятно, станет первым респондентом сам по себе, но вы всегда можете вызвать becomeFirstResponder() на NiceViewController, чтобы он стал первым респондентом и, среди прочего, , реагировать на ключевые команды.

См. документы для UIKeyCommand:

Система всегда имеет возможность первой обработать ключевые команды. Ключевые команды, которые сопоставляются с известными системными событиями (такими как вырезание, копирование и вставка), автоматически перенаправляются на соответствующие методы ответчика. Для других ключевых команд UIKit ищет объект в цепочке респондента с объектом ключевой команды, который соответствует нажатым клавишам. Если он находит такой объект, он затем проходит по цепочке респондентов в поисках первого объекта, который реализует соответствующий метод действия, и вызывает первый найденный объект.

Примечание. Пока кто-то взаимодействует с другим VC и он является первым ответившим, NiceViewController не может быть первым ответившим в то же время, поэтому вам могут понадобиться некоторые ключевые команды. другой ВК тоже.

Почему это не всегда нужно

Когда представлен только один VC, кажется, что iOS предполагает, что он будет первым ответившим, но когда у вас есть контейнерный VC, iOS, кажется, рассматривает контейнер как первый ответивший, если нет дочернего элемента, который говорит, что он может стать первым ответившим. первый ответчик.

person Matthew Seaman    schedule 01.09.2016
comment
Почему в моем примере это работает (когда есть только один дочерний контроллер представления) без переопределения canBecomeFirstResponder ? - person mgamer; 05.09.2016
comment
Я предполагаю, что он может предположить, что, поскольку существует только один венчурный капитал, он отвечает первым. Но когда у вас есть контроллер представления контейнера с некоторыми дочерними элементами, он предполагает, что первым отвечает сам контейнер, а не дочерние элементы. - person Matthew Seaman; 05.09.2016
comment
Это выглядит как солидный ответ. Один из способов увидеть, какой респондент в цепочке отвечает первым, — это использовать [[UIWindow keyWindow] firstResponder] во время выполнения и посмотреть, что происходит. Это частный API, поэтому используйте его только при отладке. - person Leo Natan; 05.09.2016

Следующее решение объяснения @Matthew добавляет запрос becomeFirstResponder(); в viewDidAppear вместо viewDidLoad решить мою аналогичную проблему. Свифт4

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

Экспериментируя с этим, я обнаружил, что если вы вручную вызываете becomesFirstResponder() на контроллерах дочернего представления, это позволяет вам иметь несколько первых ответчиков, и все ключевые команды отображаются при нажатии команды.

Я не уверен, почему это работает точно так же, как вы должны иметь только один firstResponder в любой момент времени.

person Alex Brown    schedule 12.03.2020