March 9, 2020

How to dismiss iOS keyboard programatically (Swift 5)

No items found.

"Handling Keyboard in iOS" - this has been a topic or rather a problem since the inception of iOS Development. Whether you are a beginner developer or an experienced one, you'll always be dealing with the handling of keyboards in your apps and chat applications sure won't exist without text-fields! But there's not just 1 issue with the keyboard that we are talking about here. We have to manually handle a couple of issues. Here, let me show you exactly what I mean:

When you tap on a text-field, the keyboard pops up and allows you to type in whatever you want. Now once you are done writing, there is no default done button which will dismiss the keyboard, tapping outside the keyboard ain't gonna do anything either. It's like iOS saying - "Isn't it enough I popped up the keyboard automatically for you? Figure out a way to make it go away on your own now!" Sure thing boss! If that's the protocol, we will follow.

While building chat applications, another common scenario is placing the text-field at the bottom of the screen. You tap on it and the keyboard appears and your text-field should move upwards along with the keyboard. This does not happen by default on iOS! What happens is - when you tap on the text-field, the keyboard sure appears, but now your text-field is no longer visible! The keyboard just overlaps the text-field on the screen that was placed near the bottom of the screen. Yes, you heard it right! iOS won't automatically shift the text-field as per the keyboard.

When I was a beginner in iOS development, this issue caught me by surprise! Mainly because I code in Android too and this behavior is handled very gracefully by default over there. Never knew something as simple as a keyboard would have to be handled manually.

Enough of complaints. Where there is a problem, there ought to be a solution. Our jobs as engineers and developers are to find answers to problems like this and solve them elegantly. There are a lot of ways in which people have already solved this problem. For now, I'll be walking you through the findings and solutions that I use in my day to day projects.

Problem 1 - Handling Keyboard dismissal (How do I make this Keyboard go away?)

Like we were discussing above, keyboards in iOS don’t disappear on its own. The return key on the keyboard does nothing by default! Also, there is no return key for Numeric Keypad! Tapping outside the keyboard area also does not make that thing go away! Whhattt???

Unfortunately, Apple has not yet made the dismissal of the keyboard a default thing. Hence we developers need to handle this scenario programmatically in our app. Fortunately, there are several ways in which we can achieve this functionality and that too within a few lines of code. Which way to implement in your code may depend on your exact scenario. You can judge the best one and use the same as per your requirement.

Via Tap Gesture

This is the quickest way to implement keyboard dismissal. Just set a Tap gesture on the main View and hook that gesture with a function which calls view.endEditing .
Apple docs say the below about endEditing -

Causes the view (or one of its embedded text fields) to resign the first responder status.

That’s it! Now when you tap on a text-field and keyboard appears, just tap outside anywhere on the view and your keyboard would be dismissed 🙂

The below code can be shortened into just 2 lines but I have specifically broken into functions to help make this clear as possible. I have also added comments  in the code to help understand what each function does.

{% c-block language="swift" %}
class ChatLoginVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//We make a call to our keyboard handling function as soon as the view is loaded.
initializeHideKeyboard()
}
}
extension ChatVC {
func initializeHideKeyboard(){
//Declare a Tap Gesture Recognizer which will trigger our dismissMyKeyboard() function
let tap: UITapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(dismissMyKeyboard))
//Add this tap gesture recognizer to the parent view
view.addGestureRecognizer(tap)
}
@objc func dismissMyKeyboard(){
//endEditing causes the view (or one of its embedded text fields) to resign the first responder status.
//In short- Dismiss the active keyboard.
view.endEditing(true)
}
}
}
{% c-block-end %}


Via Keyboard Return Key -

Another great option is to use that non-functional keyboard Return/Done/Continue key. It’s just sitting there doing nothing unless we have specified some custom behavior in textFieldShouldReturn function.

textFieldShouldReturn Asks the delegate if the text field should process the pressing of the return button.

  • Here you would first have to set the delegates for the text-fields.
  • Set Tag to the text-fields. What is a Tag, you ask? Apple documentation says -
  • An integer that you can use to identify view objects in your application.
    The setting of Tag is optional and would not be required when you have only 1 text-field. But here I am assigning Tag to the text-fields by incrementing their value by 1 and in the order, they are placed on the screen. This would help us identify the text-fields in code.
  • textFieldShouldReturn fires when the user presses the Return key on the keyboard. Hence, here we check - Is there any other text-field in the view whose tag is +1 greater than the current text-field on which the return key was pressed. If yes → then move the cursor to that next text-field. If No → Dismiss the keyboard
  • {% c-block language="swift" %}
    class ChatLoginVC: UIViewController {
    @IBOutlet weak var emailTF: UITextField!
    @IBOutlet weak var passwordTF: UITextField!
    override func viewDidLoad() {
    super.viewDidLoad()
    emailTF.delegate = self
    passwordTF.delegate = self
    emailTF.tag = 1
    passwordTF.tag = 2
    }
    }
    extension ChatLoginVC: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    //Check if there is any other text-field in the view whose tag is +1 greater than the current text-field on which the return key was pressed. If yes → then move the cursor to that next text-field. If No → Dismiss the keyboard
    if let nextField = self.view.viewWithTag(textField.tag + 1) as? UITextField {
    nextField.becomeFirstResponder()
    } else {
    textField.resignFirstResponder()
    }
    return false
    }
    }
    {% c-block-end %}

Via Toolbar for Number Pad -

The above solution with textFieldShouldReturn works great but unfortunately, there is no “Return” key on iOS Number Pad. To overcome our barrier here in the case of Number Pad, we can add a simple Toolbar above our Keyboard with a “Done” button. This “Done” button is going to call the same function we used in our Tap Gesture Method above and would dismiss our keyboard. To keep consistency across the fields, you can use Toolbars across the app as a common patter to dismiss your keyboard.

{% c-block language="swift" %}
class ChatLoginVC: UIViewController {
@IBOutlet weak var pinTF: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
setupToolbar()
}
func setupToolbar(){
//Create a toolbar
let bar = UIToolbar()
//Create a done button with an action to trigger our function to dismiss the keyboard
let doneBtn = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(dismissMyKeyboard))
//Create a felxible space item so that we can add it around in toolbar to position our done button
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
//Add the created button items in the toobar
bar.items = [flexSpace, flexSpace, doneBtn]
bar.sizeToFit()
//Add the toolbar to our textfield
pinTF.inputAccessoryView = bar
}
@objc func dismissMyKeyboard(){
view.endEditing(true)
}
}
{% c-block-end %}

I found a nice little extension for creating Done Button Toolbar which will help you reduce the code duplication and hook the same function across the app -
SWIFT - Add keyboard Done button using UIToolbar

If you need more insights & ways on dealing with Keyboard dismissal, I found this great article on medium, You can find it here -
Best way to dismiss Keyboard in a View Controller iOS (Swift)

Problem 2 - Moving text-fields as per the keyboard (Where is my text-field?)

We need to see our text while typing, that's basic UX. Unfortunately, when your text-field is placed at the bottom area of your screen like in our chat application, your keyboard is going to overlap it as soon as you tap on the field. We need to handle this programmatically beforehand.


What is our expected behavior here?

First, we need to check whenever a field is tapped and the keyboard appears, whether the keyboard is going to overlap the field (which in our chat application's case, it will)? If so → scroll the view such that your text-field appears just above the keyboard. If your text-field is not overlapped by the keyboard, then there is no need to shift/scroll the screen.

Enough about the problem, let’s take a deep dive into the solution.  I have added  comments in the below code to help in better understanding the role of each function in achieving  our desired goal.

{% c-block language="swift" %}
class ChatVC: UIViewController {
@IBOutlet weak var messageTF: UITextField!
@IBOutlet weak var backgroundSV: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
//Subscribe to a Notification which will fire before the keyboard will show
subscribeToNotification(UIResponder.keyboardWillShowNotification, selector: #selector(keyboardWillShowOrHide))
//Subscribe to a Notification which will fire before the keyboard will hide
subscribeToNotification(UIResponder.keyboardWillHideNotification, selector: #selector(keyboardWillShowOrHide))
//We make a call to our keyboard handling function as soon as the view is loaded.
initializeHideKeyboard()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//Unsubscribe from all our notifications
unsubscribeFromAllNotifications()
}
}
extension ChatVC {
func initializeHideKeyboard(){
//Declare a Tap Gesture Recognizer which will trigger our dismissMyKeyboard() function
let tap: UITapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(dismissMyKeyboard))
//Add this tap gesture recognizer to the parent view
view.addGestureRecognizer(tap)
}
@objc func dismissMyKeyboard(){
//endEditing causes the view (or one of its embedded text fields) to resign the first responder status.
//In short- Dismiss the active keyboard.
view.endEditing(true)
}
}
extension ChatVC {
func subscribeToNotification(_ notification: NSNotification.Name, selector: Selector) {
NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: nil)
}
func unsubscribeFromAllNotifications() {
NotificationCenter.default.removeObserver(self)
}
@objc func keyboardWillShowOrHide(notification: NSNotification) {
// Get required info out of the notification
if let scrollView = backgroundSV, let userInfo = notification.userInfo, let endValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey], let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey], let curveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] {
// Transform the keyboard's frame into our view's coordinate system
let endRect = view.convert((endValue as AnyObject).cgRectValue, from: view.window)
// Find out how much the keyboard overlaps our scroll view
let keyboardOverlap = scrollView.frame.maxY - endRect.origin.y
// Set the scroll view's content inset & scroll indicator to avoid the keyboard
scrollView.contentInset.bottom = keyboardOverlap
scrollView.scrollIndicatorInsets.bottom = keyboardOverlap
let duration = (durationValue as AnyObject).doubleValue
let options = UIView.AnimationOptions(rawValue: UInt((curveValue as AnyObject).integerValue << 16))
UIView.animate(withDuration: duration!, delay: 0, options: options, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
{% c-block-end %}

Problem 3 - Repetition of code

We don't have a single text-field in our Applications, At least not in chat applications. And these text-fields would be scattered across our entire application on different Screens. The above solution we implemented was in single ViewController. So how do we go about implementing these solutions in all the ViewControllers wherever we need to handle our Keyboard?


The first thing that would come to our minds is to add the above code snippets in all the ViewControllers wherever we need to handle such scenarios. But that means we would have to keep manually adding this code in every ViewController. There has to be a better way!
Thankfully, there is! We would be using some basic concept of OOP over here - Inheritance. We would be declaring a base keyboard handling class which would be inherited from our default UIViewController. Now wherever we need to handle our keyboard, we would just inherit our class from our base class.

  • Declare a base class for handling keyboard
  • Write our entire keyboard handling code in that base class
  • Inherit your ViewControllers from this base class and Voila! You have handled your keyboard issues elegantly.

We'll see how this looks in code -

{% c-block language="swift" %}
class ChatVC: KeyboardHandlingBaseVC {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class KeyboardHandlingBaseVC: UIViewController {
@IBOutlet weak var backgroundSV: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
subscribeToNotification(UIResponder.keyboardWillShowNotification, selector: #selector(keyboardWillShowOrHide))
subscribeToNotification(UIResponder.keyboardWillHideNotification, selector: #selector(keyboardWillShowOrHide))
initializeHideKeyboard()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
unsubscribeFromAllNotifications()
}
}
// MARK: Keyboard Dismissal Handling on Tap
private extension KeyboardHandlingBaseVC {
func initializeHideKeyboard(){
let tap: UITapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(dismissMyKeyboard))
view.addGestureRecognizer(tap)
}
@objc func dismissMyKeyboard(){
view.endEditing(true)
}
}
// MARK: Textfield Visibility Handling with Scroll
private extension KeyboardHandlingBaseVC {
func subscribeToNotification(_ notification: NSNotification.Name, selector: Selector) {
NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: nil)
}
func unsubscribeFromAllNotifications() {
NotificationCenter.default.removeObserver(self)
}
@objc func keyboardWillShowOrHide(notification: NSNotification) {
if let scrollView = backgroundSV, let userInfo = notification.userInfo, let endValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey], let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey], let curveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] {
let endRect = view.convert((endValue as AnyObject).cgRectValue, from: view.window)
let keyboardOverlap = scrollView.frame.maxY - endRect.origin.y
scrollView.contentInset.bottom = keyboardOverlap
scrollView.scrollIndicatorInsets.bottom = keyboardOverlap
let duration = (durationValue as AnyObject).doubleValue
let options = UIView.AnimationOptions(rawValue: UInt((curveValue as AnyObject).integerValue << 16))
UIView.animate(withDuration: duration!, delay: 0, options: options, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
}
{% c-block-end %}


Bonus - Let's see some Libraries

So let's say you don't want to handle any of the issues mentioned above manually. There may be various reasons for that but yeah the possibility exists. It's fine, sometimes time is of great essence. Don't worry, we have got that aspect also covered in this article. There are various libraries out there that solve the Keyboard Dismissal and TextField handling problem. I'll list down a couple so that you can go through their read-me and find for yourself whichever suits your needs.

Conclusion

So far we explored the problem of keyboard dismissal & TextField handling with keyboard. We also explored some of the ways in which we can easily solve these problems along with avoiding code repetition in our project. There is no such good or bad way when it comes to solving a technical problem. What matters the most is - At that particular moment, what works for you? what works for the project?  what works for your team? There may be times when you would need to use a mix of the above techniques or sometimes one single pattern can be followed across the app! At the end of the day what matters is that you solved the problem 🙂

That’s all from this article, do share if you all have any other ideas & approaches to solve this particular problem. I would be happy to discuss the same!