Resize the screen when keyboard appears

2020 UPDATE

Correctly using a constraint…

There is only one way to properly handle this mess in iOS.

  1. Paste KUIViewController below in to your project,

  2. Create a constraint which is very simply to the “bottom of your content”.

  3. Drag that constraint to bottomConstraintForKeyboard

KUIViewController will automatically and correctly resize your content view at all times.

Absolutely everything is totally automatic.

All Apple behaviors are handled correctly in the standard way, such as dismissing by taps, etc etc.

You are 100% completely done.

So “which view should you resize?”

You can not use .view

Because … you cannot resize the .view in iOS!!!!!! Doh!

Simply make a UIView named “holder”. It sits inside .view.

Put everything of yours inside “holder”.

Holder will of course have four simple constraints top/bottom/left/right to .view.

That bottom constraint to “holder” is indeed bottomConstraintForKeyboard.

You’re done.

Send a bill to the cliient and go drinking.

There is nothing more to do.

class KUIViewController: UIViewController {

    // KBaseVC is the KEYBOARD variant BaseVC. more on this later

    @IBOutlet var bottomConstraintForKeyboard: NSLayoutConstraint!

    @objc func keyboardWillShow(sender: NSNotification) {
        let i = sender.userInfo!
        let s: TimeInterval = (i[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let k = (i[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
        bottomConstraintForKeyboard.constant = k
        // Note. that is the correct, actual value. Some prefer to use:
        // bottomConstraintForKeyboard.constant = k - bottomLayoutGuide.length
        UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
    }

    @objc func keyboardWillHide(sender: NSNotification) {
        let info = sender.userInfo!
        let s: TimeInterval = (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        bottomConstraintForKeyboard.constant = 0
        UIView.animate(withDuration: s) { self.view.layoutIfNeeded() }
    }

    @objc func clearKeyboard() {
        view.endEditing(true)
        // (subtle iOS bug/problem in obscure cases: see note below
        // you may prefer to add a short delay here)
    }

    func keyboardNotifications() {
        NotificationCenter.default.addObserver(self,
            selector: #selector(keyboardWillShow),
            name: UIResponder.keyboardWillShowNotification,
            object: nil)
        NotificationCenter.default.addObserver(self,
            selector: #selector(keyboardWillHide),
            name: UIResponder.keyboardWillHideNotification,
            object: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        keyboardNotifications()
        let t = UITapGestureRecognizer(target: self, action: #selector(clearKeyboard))
        view.addGestureRecognizer(t)
        t.cancelsTouchesInView = false
    }
}

Simply …

Use KUIViewController anywhere a keyboard might appear.

class AddCustomer: KUIViewController, SomeProtocol {

class EnterPost: KUIViewController {

class EditPurchase: KUIViewController {

On those screens absolutely everything is now completely automatic regarding the keyboard.

You’re done.

Phew.


*Minor footnote – background clicks correctly dismiss the keyboard. That includes clicks which fall on your content. This is correct Apple behavior. Any unusual variation from that would take a huge amount of very anti-Apple custom programming.

*Extremely minor footnote – so, any and all buttons on the screen will work 100% correctly every time. However in the INCREDIBLY obscure case of nested (!) container views inside nested (!) scroll views with nested (!) page view containers (!!!!), you may find that a button will seemingly not work. This seems to be basically a (obscure!) problem in current iOS. If you encounter this incredibly obscure problem, fortunately the solution is simple. Looking at the function clearKeyboard(), simply add a short delay, you’re done.

@objc func clearKeyboard() {
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
        self.view.endEditing(true)
    }
}

(A great tip from user @wildcat12 https://stackoverflow.com/a/57698468/294884 )

Leave a Comment