Extend iOS 11 Safe Area to include the keyboard Extend iOS 11 Safe Area to include the keyboard swift swift

Extend iOS 11 Safe Area to include the keyboard


What seems to be working for me is to calculate the intersection between view.safeAreaLayoutGuide.layoutFrame and the keyboard frame, and then setting the height of that as the additionalSafeAreaInsets.bottom, instead of the whole keyboard frame height. I don't have a toolbar in my view controller, but I do have a tab bar and it is accounted for correctly.

Complete code:

import UIKitpublic extension UIViewController {    func startAvoidingKeyboard()     {            NotificationCenter.default            .addObserver(self,                         selector: #selector(onKeyboardFrameWillChangeNotificationReceived(_:)),                         name: UIResponder.keyboardWillChangeFrameNotification,                         object: nil)    }    func stopAvoidingKeyboard()     {        NotificationCenter.default            .removeObserver(self,                            name: UIResponder.keyboardWillChangeFrameNotification,                            object: nil)    }    @objc    private func onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification)     {        guard             let userInfo = notification.userInfo,            let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue         else {            return        }        let keyboardFrameInView = view.convert(keyboardFrame, from: nil)        let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)        let intersection = safeAreaFrame.intersection(keyboardFrameInView)        let keyboardAnimationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]        let animationDuration: TimeInterval = (keyboardAnimationDuration as? NSNumber)?.doubleValue ?? 0        let animationCurveRawNSN = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber        let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue        let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw)        UIView.animate(withDuration: animationDuration,                       delay: 0,                       options: animationCurve,                       animations: {            self.additionalSafeAreaInsets.bottom = intersection.height            self.view.layoutIfNeeded()        }, completion: nil)    }}


If you need support back to pre IOS11 versions you can use the function from Fabio and add:

if #available(iOS 11.0, *) { }

final solution:

extension UIViewController {    func startAvoidingKeyboard() {        NotificationCenter.default.addObserver(self,                                               selector: #selector(_onKeyboardFrameWillChangeNotificationReceived(_:)),                                               name: NSNotification.Name.UIKeyboardWillChangeFrame,                                               object: nil)    }    func stopAvoidingKeyboard() {        NotificationCenter.default.removeObserver(self,                                                  name: NSNotification.Name.UIKeyboardWillChangeFrame,                                                  object: nil)    }    @objc private func _onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) {        if #available(iOS 11.0, *) {            guard let userInfo = notification.userInfo,                let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {                    return            }            let keyboardFrameInView = view.convert(keyboardFrame, from: nil)            let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom)            let intersection = safeAreaFrame.intersection(keyboardFrameInView)            let animationDuration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0            let animationCurveRawNSN = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber            let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIViewAnimationOptions.curveEaseInOut.rawValue            let animationCurve = UIViewAnimationOptions(rawValue: animationCurveRaw)            UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: {                self.additionalSafeAreaInsets.bottom = intersection.height                self.view.layoutIfNeeded()            }, completion: nil)        }    }}


I use a different approach. I have a view (KeyboardProxyView) that I add to my view hierarchy.I pin this to the bottom of the main view, and adjust its height with the keyboard. This means we can treat the keyboardProxy as if it is the keyboard view - except that it is a normal view, so you can use constraints on it.

This allows me to constrain my other views relative to the keyboardProxy manually.

e.g. - my toolbar isn't constrained at all, but I might have inputField.bottom >= keyboardProxy.top

Code below(note - I use HSObserver and PureLayout for notifications and autolayout - but you could easily rewrite that code if you prefer to avoid them)

import Foundationimport UIKitimport PureLayoutimport HSObserver/// Keyboard Proxy view will mimic the height of the keyboard/// You can then constrain other views to move up when the KeyboardProxy expands using AutoLayoutclass KeyboardProxyView: UIView, HSHasObservers {    weak var keyboardProxyHeight: NSLayoutConstraint!    override func didMoveToSuperview() {        keyboardProxyHeight = self.autoSetDimension(.height, toSize: 0)        let names = [UIResponder.keyboardWillShowNotification,UIResponder.keyboardWillHideNotification]        HSObserver.init(forNames: names) { [weak self](notif) in            self?.updateKeyboardProxy(notification: notif)        }.add(to: self)        activateObservers()    }    var parentViewController: UIViewController? {        var parentResponder: UIResponder? = self        while parentResponder != nil {            parentResponder = parentResponder!.next            if let viewController = parentResponder as? UIViewController {                return viewController            }        }        return nil    }    func updateKeyboardProxy(notification:Notification){        let userInfo = notification.userInfo!        let animationDuration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue        let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue        let convertedKeyboardEndFrame = self.superview!.convert(keyboardEndFrame, from: self.window)        let rawAnimationCurve = (notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).uint32Value << 16        let animationCurve = UIView.AnimationOptions(rawValue:UInt(rawAnimationCurve))        keyboardProxyHeight.constant = self.superview!.bounds.maxY - convertedKeyboardEndFrame.minY        //keyboardProxyHeight.constant = keyboardEndFrame.height        UIView.animate(withDuration: animationDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {            self.parentViewController?.view.layoutIfNeeded()        }, completion: nil)    }}