Extend iOS 11 safe zone to enable keyboard

The new Safe Area setup guide introduced in iOS 11 is great for preventing content from appearing below the columns, but excluding the keyboard. This means that when the keyboard is displayed, the content is still hiding behind it, and this is the problem I'm trying to solve.

My approach is based on listening to keyboard notifications and then setting up a safe area through additionalSafeAreaInsets .

Here is my code:

 override func viewDidLoad() { let notificationCenter = NotificationCenter.default notificationCenter.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) notificationCenter.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) notificationCenter.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil) } //MARK: - Keyboard extension MyViewController { @objc func keyboardWillShow(notification: NSNotification) { let userInfo = notification.userInfo! let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0) UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded(); } } @objc func keyboardWillHide(notification: NSNotification) { additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded(); } } @objc func keyboardWillChange(notification: NSNotification) { let userInfo = notification.userInfo! let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0) UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded(); } } } 

This works well since MyController is a UIViewController with a UITableView that goes through the entire safe area. Now, when the keyboard appears, the lower part extends so that no cells are located behind the keyboard.

The problem is the lower bars. I also have a toolbar at the bottom, which is already included in the safe zone. Thus, setting the full height of the keyboard as an additional safe insertion area pushes the bottom of the table up too much, exactly the height of the bottom panel. For this method to work well, I have to set additionalSafeAreaInsets.bottom equal to the height of the keyboard minus the height of the bottom panel.

Question 1: What is the best way to get the current safe gap in the area below? Manually get the toolbar frame and use its height? Or can you get a space directly from the safe zone planning guide?

Question 2: It should supposedly be possible for the bottom panel to resize without resizing the keyboard, so I also have to implement some method while listening to the change in the panel border. Is this best done in viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) ? Or in another place?

thanks

+10
source share
3 answers

Which seems to work for me to calculate the intersection between view.safeAreaLayoutGuide.layoutFrame and the keyboard frame, and then set the height, which is as additionalSafeAreaInsets.bottom , and not the entire height of the keyboard frame. I don't have a toolbar in my view controller, but I have a tab bar and it counts correctly.

Full code:

 import UIKit public 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) } } 
+16
source

If you need support for previous versions of IOS11, you can use the function from Fabio and add:

 if #available(iOS 11.0, *) { } 

final decision:

 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) } } } 
+5
source

The exception of the lower safe zone worked for me:

  NSValue keyboardBounds = (NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.FrameEndUserInfoKey); _bottomViewBottomConstraint.Constant = keyboardBounds.RectangleFValue.Height - UIApplication.SharedApplication.KeyWindow.SafeAreaInsets.Bottom; View.LayoutIfNeeded(); 
0
source

Source: https://habr.com/ru/post/1270362/


All Articles