Search code examples
iosipadkeyboardmultitaskingsplitview

Handle another app's obscuring keyboard on iPad split view (iOS 9 multitasking)


Previously if one presented a keyboard on one's own app one would embed everything in a UIScrollView and adjust the contentInset to keep content from being obscured by the keyboard.

Now with split view multitasking on iOS 9 the keyboard may appear at any moment and stay visible even while the user is no longer interacting with the other app.

Question

Is there an easy way to adapt all view controllers that were not expecting the keyboard to be visible and without start embedding everything in scrollviews?


Solution

  • The secret is to listen to the UIKeyboardWillChangeFrame notification that is triggered whenever the keyboard is shown/hidden from your app or from another app running side by side with yours.

    I created this extension to make it easy to start/stop observing those events (I call them in viewWillAppear/Disappear), and easily get the obscuredHeight that is usually used to adjust the bottom contentInset of your table/collection/scrollview.

    @objc protocol KeyboardObserver
    {
        func startObservingKeyboard() // Call this in your controller's viewWillAppear
        func stopObservingKeyboard() // Call this in your controller's viewWillDisappear
        func keyboardObscuredHeight() -> CGFloat
        @objc optional func adjustLayoutForKeyboardObscuredHeight(_ obscuredHeight: CGFloat, keyboardFrame: CGRect, keyboardWillAppearNotification: Notification) // Implement this in your controller and adjust your bottom inset accordingly
    }
    
    var _keyboardObscuredHeight:CGFloat = 0.0;
    
    extension UIViewController: KeyboardObserver
    {
        func startObservingKeyboard()
        {
            NotificationCenter.default.addObserver(self, selector: #selector(observeKeyboardWillChangeFrameNotification(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
        }
    
        func stopObservingKeyboard()
        {
            NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
        }
    
        func observeKeyboardWillChangeFrameNotification(_ notification: Notification)
        {
            guard let window = self.view.window else {
                return
            }
    
            let animationID = "\(self) adjustLayoutForKeyboardObscuredHeight"
            UIView.beginAnimations(animationID, context: nil)
            UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: (notification.userInfo![UIKeyboardAnimationCurveUserInfoKey]! as AnyObject).intValue)!)
            UIView.setAnimationDuration((notification.userInfo![UIKeyboardAnimationCurveUserInfoKey]! as AnyObject).doubleValue)
    
            let keyboardFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue
            _keyboardObscuredHeight = window.convert(keyboardFrame!, from: nil).intersection(window.bounds).size.height
            let observer = self as KeyboardObserver
            observer.adjustLayoutForKeyboardObscuredHeight!(_keyboardObscuredHeight, keyboardFrame: keyboardFrame!, keyboardWillAppearNotification: notification)
    
            UIView.commitAnimations()
        }
    
        func keyboardObscuredHeight() -> CGFloat
        {
            return _keyboardObscuredHeight
        }
    }