Setting up keyboard avoidance to a textview using keyboardLayoutGuide

I'm setting up keyboard avoidance to a textview.

Objectives: Use keyboardLayoutGuide.followsUndockedKeyboard Allow for floating keyboard on iPad Move the textView up to clear the keyboard when it's tapped Move the textView back when the keyboard is hidden or when the textView is not firstResponder

View layout for testing: Three controls. A textField A TextView A UIView

The constraint on the bottom of the textView is 15 pts from the top of the UIView. This constraint is set to a Priority of 750

The code I have works fine except when the iPad is rotated. keyboardWillHideNotification fires when the rotation occurs even though the keyboard stays undocked.

This causes the textView to drop to its home position and then pop back up again. It's very goofy looking. The textView should hug the top of the keyboard.

I tried using textViewDidEndEditing instead of keyboardWillHide but that isn't much better.

Anybody have any ideas on making the textView hug the top of the keyboard when the iPad is rotated?

class ViewController: UIViewController, UITextViewDelegate
{
    @IBOutlet weak var myTextView: UITextView!
    
    private var buttomConstraint: NSLayoutConstraint!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        myTextView.translatesAutoresizingMaskIntoConstraints = false
        buttomConstraint = myTextView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor)
        view.keyboardLayoutGuide.followsUndockedKeyboard = true

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
        
        
        hideKeyboard()
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
    {
        
    }
    
    @objc func keyboardWillHide(notification: NSNotification)
    {
        print("Keyboard will hide")
        
        if myTextView.isFirstResponder
        {
            UIView.animate(withDuration: 0.3) { [weak self] in
                self?.buttomConstraint.isActive = false
                self?.view.layoutIfNeeded()
            }
        } else {
            print("Somethibg")
        }
    }
    
    @objc func keyboardWillShow(notification: NSNotification)
    {
        if myTextView.isFirstResponder
        {
            UIView.animate(withDuration: 0.3) { [weak self] in
                self?.buttomConstraint.isActive = true
                self?.view.layoutIfNeeded()
            }
        } else {
            buttomConstraint.isActive = false
        }
    }
    
    func textViewDidBeginEditing(_ textView: UITextView)
    {
//        UIView.animate(withDuration: 0.3) { [weak self] in
//            self?.buttomConstraint.isActive = true
//            self?.view.layoutIfNeeded()
//        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView)
    {
//        UIView.animate(withDuration: 0.3) { [weak self] in
//            self?.buttomConstraint.isActive = false
//            self?.view.layoutIfNeeded()
//        }
    }
}

extension ViewController
{
    // MARK: This dismisses the keyBoard when the view it tapped
    func hideKeyboard()
    {
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(
            target: self,
            action: #selector(ViewController.dismissKeyboard))

        tap.cancelsTouchesInView = false
        view.addGestureRecognizer(tap)
    }

    @objc func dismissKeyboard()
    {
        view.endEditing(true)
    }
}

Replies

In general, keyboardLayoutGuide should be used instead of keyboard notifications, not with them. In this case, you definitely don't need any keyboard notifications. You could have your bottom constraint be something like this instead:

    bottomConstraint = view.keyboardLayoutGuide.topAnchor.constraint(equalTo: myTextView.bottomAnchor)
    bottomConstraint.priority = UILayoutPriority.defaultHigh

and then your constraint from your text view to your placeholder view could be a required constraint that's greaterThanOrEqualTo:, such as:

myView.topAnchor.constraint(greaterThanOrEqualToSystemSpacingBelow: myTextView.bottomAnchor, multiplier: 1.0)

Remove the activating/deactivating in the notifications, and you have a set of constraints that work for your situation!

Also, for more complicated scenarios, remember that UIKeyboardLayoutGuide inherits from UITrackingLayoutGuide, and allows for automatic activation and deactivation of constraints when the floating keyboard approaches an edge. You may need this for your text view so that it doesn't end up being unusable when the floating keyboard is near the top; for that, just set the constraint(s) like this instead of activating it with the other constraints:

view.keyboardLayoutGuide.setConstraints([bottomConstraint], activeWhenAwayFrom: .top)

I really appreciate your willingness to help with my dilemma. I'v been beating on this for more than a weeks time.

I couldn't get the code snippets you shared to work properly.

What would really help is if you can find the time to put together a demo app using IB that works and then share that code. I realize that's a lot to ask but it would probably help a ton of there developers too.

Objectives: Use IB for the Layout. Use keyboardLayoutGuide.followsUndockedKeyboard. Allow for floating keyboard on iPad. Move the textView up to clear the keyboard when it's tapped. Move the textView to home position when the keyboard is hidden or when the textView is not firstResponder (When the textField is tapped). Hide the keyboard when the view is tapped. If textview is first responder when the keyboard is changed to floating, move the textView to nome position. On iPad, when the device is rotated and the textView is firstResponder, have the keyboard hug the top of the textview.