I’ve struggled with this for, oh, about a decade )-: Eventually I moved the core code out into a separate helper type (pasted in below). If my content is in a scroll view, I set it up and respond as follows:
var avoider: KeyboardAvoider? = nil
override func viewDidLoad() {
…
let avoider = KeyboardAvoider(view: self.mainText)
avoider.delegate = self
self.avoider = avoider
…
}
func inset(by inset: CGFloat, keyboardAvoider: KeyboardAvoider) {
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: inset, right: 0.0)
self.mainText.contentInset = insets
self.mainText.scrollIndicatorInsets = insets
// UITextView seems to take care of the 'scoll active text to be visible' problem.
}
If my content is just a bunch of separate views I put an invisible wrapper view around them and then set up a constraint in order to inset that wrapper view. My delegate callback adjusts the constant on that constraint, like this:
func inset(by inset: CGFloat, keyboardAvoider: KeyboardAvoider) {
self.bottomConstraint.constant = inset
}
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
import UIKit
class KeyboardAvoider : NSObject {
typealias Delegate = KeyboardAvoiderDelegate
private override init() {
fatalError()
}
required init(view: UIView) {
self.view = view
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(note:)), name: .UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(note:)), name: .UIKeyboardWillHide, object: nil)
}
let view: UIView
weak var delegate: Delegate? = nil
deinit {
NotificationCenter.default.removeObserver(self)
}
private static let logTransitions = false
@objc func keyboardDidShow(note: Notification) {
let new = (note.userInfo![UIKeyboardFrameEndUserInfoKey]! as! NSValue).cgRectValue
let newInViewCoordinates = self.view.superview!.convert(new, from: UIScreen.main.coordinateSpace)
let keyboardTop = newInViewCoordinates.minY
let viewBottom = self.view.frame.maxY
let inset: CGFloat
if keyboardTop < viewBottom {
inset = viewBottom - keyboardTop
} else {
inset = 0.0
}
if (KeyboardAvoider.logTransitions) {
NSLog("did show, view.bottom: %.1f, keyboard.top: %.1f, inset: %.1f", viewBottom, keyboardTop, inset)
}
self.delegate?.inset(by: inset, keyboardAvoider: self)
}
@objc func keyboardWillHide(note: Notification) {
if (KeyboardAvoider.logTransitions) {
NSLog("did hide, inset: 0.0")
}
self.delegate?.inset(by: 0.0, keyboardAvoider: self)
}
}
protocol KeyboardAvoiderDelegate : AnyObject {
func inset(by inset: CGFloat, keyboardAvoider: KeyboardAvoider)
}