If you’re moving your app’s interface from under the keyboard when it appears, you’re probably doing the following:
1. Get a reference to the relevant text field in the UITextFieldDelegate‘s textFieldDidBeginEditing method.
2. Listen to a relevant keyboard notification (possibly UIKeyboardWillShow or UIKeyboardWillHide but probably the best to use is UIKeyboardWillChangeFrame, as this covers all your bases). Get the size of the keyboard from the userInfo property and move the textField up out of the way.
Great, this works because when the user taps on a text field, the events occur in this order:
1. textFieldDidBeginEditing
2. UIKeyboardWillChangeFrame
But then when you happen to implement a text view you’ll discover that the events occur in the opposite order:
1. UIKeyboardWillChangeFrame
2. textViewDidBeginEditing
Whaaa? Well, that messes up the steps we were following – when we get the UIKeyboardWillChangeFrame notification, we don’t yet have a reference to the relevant text view to move it!
How to solve this? Here are three approaches:
1. Use UIKeyboardDidChangeFrame (Did, not Will) instead, to be sure we get it in the right order. Problem – we can no longer animate interface changes simultaneously with the keyboard, rather animations will happen in sequence.
2. Store the keyboard size in a property in the UIKeyboardWillHide selector. Call a method (let’s call it moveInterface() ) after both steps that will move the interface out of the way. The moveInterface() method will only work when it has references to both the keyboard size, and the relevant text field / text view.
3. Here’s another option, thinking outside the box:
The reason why we need to get a reference to the text field/view in the …didBeginEditing method, is that Apple hasn’t given us a simple way to get a reference to the current field/view being edited (also known as the firstResponder). However, Apple has given us an isFirstResponder() method that will tell you if a view is currently the fist responder. Great! We can use that method to recursively iterative through a view’s subviews and determine the current first responder.
If we know the first responder, we don’t need the …didBeginEditing methods at all, and can skip straight to dealing with moving the interface when we receive the UIKeyboardWillChangeFrame notification.
Here’s a UIView extension to add a computed property that returns the first responder from a view’s subviews:
import UIKit extension UIView { var firstResponder:UIView? { if self.isFirstResponder() { return self } for view in self.subviews { if let firstResponder = view.firstResponder { return firstResponder } } return nil } }
And here’s a UIViewController extension to get the scene’s first responder:
extension UIViewController { var firstResponder:UIView? { return view.firstResponder } }
[…] Keyboards, text views and first responders […]