Tin Luong
Tin Luong

Reputation: 141

keyboardFrameEndUserInfoKey not showing correct values?

I have a text input field at the bottom of my view, which I'm trying to animate up and down to stay on top of the keyboard.

func setupKeyboardObservers() {
    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillChangeFrame), name: UIResponder.keyboardWillHideNotification, object: nil)
}

@objc func handleKeyboardWillChangeFrame(notification: NSNotification) {

    let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
    let keyboardDuration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double)

    print(keyboardFrame)
    orderDetailView?.textInputViewBottomAnchor?.constant = -keyboardFrame!.height
    UIView.animate(withDuration: keyboardDuration!) {
        self.view.layoutIfNeeded()
    }
}

OrderDetailView is the view for the viewcontroller.

The textinputview is the part that animates, and it works correctly when the keyboard first shows up, but does not animate back when I send a message and the keyboard resigns first responder, nor if I resignfirstresponder by clicking outside the keyboard.

When I print the cgrect value from keyboardFrameEndUserInfoKey, it returns the same frame value as when the keyboard is present (instead of 0).

This only seems to work properly when I drag down the keyboard from the view.

Thanks for your help.

Upvotes: 4

Views: 3645

Answers (1)

Matic Oblak
Matic Oblak

Reputation: 16774

In your case the height is still non-zero when keyboard hides which I assume is your issue. You need to convert keyboard frame to your view coordinate system and setup constraints according to that. Check the following:

@objc private func onKeyboardChange(notification: NSNotification) {
    guard let info = notification.userInfo else { return }
    guard let value: NSValue = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
    let newFrame = value.cgRectValue

    if let durationNumber = info[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber, let keyboardCurveNumber = info[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber {
        let duration = durationNumber.doubleValue
        let keyboardCurve = keyboardCurveNumber.uintValue
        UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions(rawValue: keyboardCurve), animations: {
            self.updateFromKeyboardChangeToFrame(newFrame)
        }, completion: { _ in
            // After animation
        })
    } else {
        self.updateFromKeyboardChangeToFrame(newFrame)
    }
}

private func updateFromKeyboardChangeToFrame(_ keyboardFrame: CGRect) {
    let view: UIView! // Whatever view that uses bottom constraint
    let bottomConstraint: NSLayoutConstraint! // Your bottom constraint

    let constant = view.bounds.height-max(0, view.convert(keyboardFrame, from: nil).origin.y)
    bottomConstraint.constant = max(0, constant)
    view.layoutIfNeeded()
}

In your case you seem to use

let view = self.view
let bottomConstraint = orderDetailView?.textInputViewBottomAnchor

and it depends on how you define your constraint but it seems you will need to use negative values as bottomConstraint.constant = -max(0, constant).

Upvotes: 7

Related Questions