The new Safe Area layout guide introduced in iOS 11 works great to prevent content from displaying below bars, but it excludes the keyboard. That means that when a keyboard is displayed, content is still hidden behind it and this is the problem I am trying to solve.
My approach is based on listening to keyboard notifications and then adjusting the 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 as the MyController
is a UIViewController
with a UITableView
that extends through the whole safe area. Now when the keyboard appears, the bottom is pushed up so that no cells are behind the keyboard.
The problem is with bottom bars. I also have a toolbar at the bottom which is already included in the safe area. Therefore, setting full keyboard height as additional safe area inset pushes the bottom of the table view up too much by exactly the height of the bottom bar. For this method to work well, I must set the additionalSafeAreaInsets.bottom
to be equal to the keyboard height minus the height of the bottom bar.
Question 1: What is the best way to get the current safe area gap on the bottom? Manually get frame of toolbar and use its height? Or is it possible to get the gap directly from the safe area layout guide?
Question 2: Presumably it should be possible for the bottom bar to change size without the keyboard changing size so I should also implement some method listening to change in frame of the bar. Is this best done in viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
? Or elsewhere?
Thank you
You obtain the safe area for a view by applying the insets in this property to the view's bounds rectangle. If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the edge insets in this property are 0 .
The safe area represents the portion of your screen that is unobscured by bars and other operating system based content. Safe area is pre-defined by iOS across all Apple devices and is present in Android devices as well.
The safe area layout guide is a property of the UIView class and it inherits from the UILayoutGuide class. It was introduced in iOS 11 and tvOS 11. It helps you correctly position views in a layout. The safe area layout guide replaces the top and bottom layout guides of the UIViewController class.
To use the new safe area, select Safe Area Layout Guides in the File inspector for the view controller, and then add constraints between your content and the new safe area anchors. This prevents your content from being obscured by top and bottom bars, and by the overscan region on tvOS.
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 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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With