Представление контроллера модального представления, когда клавиатура активна

Итак, у меня есть форма, состоящая из нескольких текстовых полей. Пользователь вводит в поля как обычно. Но у пользователя также есть возможность двойного нажатия на текстовое поле, которое представляет собой контроллер модального представления, позволяющий пользователю выбирать из нескольких параметров, относящихся к этому полю.

Могу ли я каким-то образом представить модальную "над" клавиатуру, чтобы при ее увольнении клавиатура по-прежнему активна для поля, которое было первым ответчиком, прежде чем я представил модальный?

В настоящий момент клавиатура отклоняется, пока появляется модальная мода, и снова появляется, когда модаль отклоняется. Он выглядит неуклюжим для меня и отвлекающим. Хотелось бы упорядочить его и уменьшить количество анимации на экране.

Ответ 1

Изменение: я обновил этот ответ для iOS 12 и Swift. Пересмотренный пример проекта (содержащий новые реализации Swift и обновленные реализации Objective-C) находится здесь.


Вы можете создать новое окно UIWindow и поместить его поверх окна по умолчанию, одновременно скрывая окно клавиатуры.


Animated example of overlaying a new UIWindow over the existing one


У меня есть пример проекта на Github здесь, но основной процесс ниже.

  • Создайте новый класс UIViewController для вашего модального представления. Я назвал мой OverlayViewController. Настройте соответствующий вид, как вы хотите. На ваш вопрос вам нужно передать некоторые опции, поэтому я создал протокол делегирования OverlayViewController и сделаю контроллер корневого представления основного окна (класс ViewController) нашим делегатом.
protocol OverlayViewControllerDelegate: class {
  func optionChosen(option: YourOptionsEnum)
}
  • Добавьте некоторые вспомогательные свойства в наш оригинальный контроллер вида.
class ViewController: UIViewController {
  /// The text field that responds to a double-tap.
  @IBOutlet private weak var firstField: UITextField!
  /// A simple label that shows we received a message back from the overlay.
  @IBOutlet private weak var label: UILabel!
  /// The window that will appear over our existing one.
  private var overlayWindow: UIWindow?
  • Добавьте UITapGestureRecognizer в ваш UITextField.
override func viewDidLoad() {
  super.viewDidLoad()

  // Set up gesture recognizer
  let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
  doubleTapRecognizer.numberOfTapsRequired = 2
  doubleTapRecognizer.delegate = self

  firstField.addGestureRecognizer(doubleTapRecognizer)

  firstField.becomeFirstResponder()
}
  • UITextField имеет встроенный распознаватель жестов, поэтому нам нужно разрешить нескольким UIGestureRecognizer работать одновременно.
extension ViewController: UIGestureRecognizerDelegate {
  // Our gesture recognizer clashes with UITextField's.
  // Need to allow both to work simultaneously.
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                         shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }
}
  • Это интересная часть. Когда распознаватель жестов сработает, создайте новое OverlayViewController UIWindow, назначьте свой OverlayViewController в качестве корневого контроллера представления и покажите его. Обратите внимание, что мы установили уровень окна на UIWindowLevelAlert чтобы он отображался спереди. Тем не менее, клавиатура все еще будет впереди, несмотря на уровень окна предупреждения, поэтому мы должны также вручную скрыть ее окно.

Важно не устанавливать новое UIWindow качестве клавиши или изменять первый респондент из UITextField иначе клавиатура будет отклонена.

Ранее (до iOS 10?) Мы могли обойтись с overlayWindow.makeKeyAndVisible(), но теперь установка его в качестве клавиши приведет к отключению клавиатуры. Кроме того, окно клавиатуры теперь имеет нестандартное значение UIWindow.Level которое находится перед каждым публично определенным значением. Я работал над этим, находя окно клавиатуры в иерархии и скрывая его.

@objc func handleDoubleTap() {
    // Prepare the overlay window
    guard let overlayFrame = view?.window?.frame else { return }
    overlayWindow = UIWindow(frame: overlayFrame)
    overlayWindow?.windowLevel = .alert
    let overlayVC = OverlayViewController.init(nibName: "OverlayViewController", bundle: nil)
    overlayWindow?.rootViewController = overlayVC
    overlayVC.delegate = self

    // The keyboard window always appears to be the last in the hierarchy.
    let keyboardWindow = UIApplication.shared.windows.last
    keyboardWindow?.isHidden = true
}
  • Окно наложения теперь является исходным окном. Теперь пользователь может выбрать любые параметры, встроенные в представление наложения. После того, как ваш пользователь выберет опцию, ваш делегат должен предпринять любое действие, которое вы запланировали, а затем закрыть окно наложения и снова показать клавиатуру.
func optionChosen(option: YourOptionsEnum) {
  // Your code goes here. Take action based on the option chosen.
  // ...

  // Dismiss the overlay and show the keyboard
  overlayWindow = nil;
  UIApplication.shared.windows.last?.isHidden = false
}
  • Оверлейное окно должно исчезнуть, и ваше исходное окно должно появиться с клавиатурой в том же положении, что и раньше.

Ответ 2

Я не могу попробовать это прямо сейчас, но реализовал аналогичные для других целей. В действии для представления модального контроллера я предполагаю распознаватель жестов или метод делегирования, сначала сделайте снимок экрана и поместите его в imageView по текущим subviews. Позже, возвращаясь, просто удалите imageView.

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

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

Ответ 3

@Rob Bajorek ответ отличный. Для iOS 9,10 есть небольшие изменения.
 Вместо кода:

[self.overlayWindow setWindowLevel:UIWindowLevelAlert];
[self.overlayWindow makeKeyAndVisible];

Введите следующий код:

NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *lastWindow = (UIWindow *)[windows lastObject];
[self.overlayWindow setWindowLevel:lastWindow.windowLevel + 1];
[self.overlayWindow setHidden:NO];

Ответ 4

Для того чтобы клавиатура была видимой, любой текст, принимающий поля, такие UITextField or UITextView or UISearchBar должны быть первым ответчиком, и они должны быть видимыми в представлении. Значение отвечающего представления должно быть в иерархии верхнего уровня в окне.

Если вам не нужен этот эффект, вместо представления ViewController вы можете добавить ViewController.view в качестве подзаголовка вашего self.view с анимацией.

Ответ 5

У вас есть доступ к кадру клавиатуры в iOS.

Вам нужно реализовать код для прослушивания уведомлений клавиатуры (например, UIKeyboardWillShowNotification и UIKeyboardWillChangeFrameNotification). Уведомление отправит вам информацию о кадре клавиатуры.

Посмотрите на описание "Ключей информации о ключах клавиатуры" в справочной системе Windows.

Вы найдете полезную для себя цель:

UIKeyboardBoundsUserInfoKey Ключ для объекта NSValue, содержащий CGRect, который идентифицирует прямоугольник границ клавиатура в координатах окна. Этого значения достаточно для получения размер клавиатуры. Если вы хотите получить источник клавиатура на экране (до или после анимации) используйте значения полученной из словаря пользовательской информации через UIKeyboardCenterBeginUserInfoKey или UIKeyboardCenterEndUserInfoKey константы.

С информацией о ракурсе клавиатуры вы можете показать там модальный вид.

Ответ 6

Просто добавьте жест нажатия, расположенный в текстовом поле, и поле 'flagTextfield' поля UITextfield;

UITapGestureRecognizer* doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(DoubleTapMethod:)];
doubleTap.numberOfTapsRequired = 2;
[self.txtTest addGestureRecognizer:doubleTap];


-(void)DoubleTapMethod:(UITapGestureRecognizer *)gesture
{

     [flagTextfield resignFirstResponder];
     NSLog(@"DoubleTap detected");
     //Set your logic on double tap of Textfield...
     //presents a modal view controller
}

- (void)textFieldDidBeginEditing:(UITextField *)textField{

        flagTextfield = textfield;
}