UIKeyboardWillChangeFrame not called on iOS 11 (when transitioning view controllers)

Viewed 1371

Issue

I have a number of cases on iOS 11 (that do not occur on iOS 10 and below) where .UIKeyboardWillChangeFrame notification is not being fired — specifically when transitioning between view controllers where both view controllers have a UITextfield which is set as the fristResponder.

Since I have UI that needs to animate above the keyboard in response to the keyboard showing, receiving this notification is essential.

On iOS 10 and below, I get the notification on both view controllers (on showing VC A and also when pushing VC B). However, on iOS 11, the notification does not fire when pushing VC B. It's as if the keyboard remained in place.

Does anyone know what the cause of this is?


Details

Both view controllers inherit from a base view controller with the following implementation:

class BaseViewController {
override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil)

}

// Update layout when the keyboard is shown or hidden
@objc func keyboardWillChangeFrame(notification : Notification) {

    // Check if got info
    if (notification.userInfo == nil) {
        return;
    }

    // Get resize properties
    let dict = notification.userInfo!
    let rect = self.view.convert((((dict[UIKeyboardFrameEndUserInfoKey as NSObject] as Any) as AnyObject).cgRectValue)!, from: nil)
    let size = self.view.bounds.size.height - rect.origin.y
    let duration = ((dict[UIKeyboardAnimationDurationUserInfoKey] as Any) as AnyObject).doubleValue
    let curve = UIViewAnimationCurve.init(rawValue: (((dict[UIKeyboardAnimationCurveUserInfoKey] as Any) as AnyObject).intValue)!)
    self.keyboardOffset = max(0, size)

    // Set animation options
    var options : UIViewAnimationOptions
    switch (curve!) {
    case .easeInOut:
        options = UIViewAnimationOptions()
    case .easeIn:
        options = UIViewAnimationOptions.curveEaseIn
    case .easeOut:
        options = UIViewAnimationOptions.curveEaseOut
    case .linear:
        options = UIViewAnimationOptions.curveLinear
    }

    // Animate the change
    UIView.animate(withDuration: duration!, delay: 0, options: options, animations: { () -> Void in

        // Relayout
        self.relayout()

    }, completion: nil)

}
}

Example of a subclass:

class ViewControllerA: BaseViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        passwordField.becomeFirstResponder()
    }

    func relayout() {
    // ... do animations to show stuff above keyboard.
    }

}

Note that .keyboardWillShow and .keybaordWillHide are also not fired on the transition to VC B.

2 Answers

You need to add self.view.endEditing(force:Bool) in you first view controller's viewWillDisappear method.

Generally, you can always call endEditing method when you changing the screen.

I've worked around this by saving the keyboard height in a class variable in VC A and then passing that along to VC B. It's not ideal, as I would prefer my VC's to be independent for those details.

Related