While working on the SwiftUI Chat tutorial, we ran into the issue of handling the keyboard animations by adjusting the layout of all the views visible on the screen, depending on whether the keyboard is visible or not. In this tutorial, we are going to learn how to handle the keyboard animations in SwiftUI with the help of ObservedObject.
Even when everything seems to work perfectly, UI wise, there’s always one more interesting part to improve. In this case, it’s handling the keyboard response, which we didn’t do as part of the chat screen tutorial. This lesson not only applies to this screen but also to many other places containing TextFields. So, if you don’t want to read the chat screen tutorial first, but jump into this article right away, be my guest. The previous tutorial mainly gives you a specific practical case but it is not much related to this article logically. The source code for this tutorial is here.

handling keyboard swiftui

1. The problem – and the solution

Imagine we have a chat app in which a TextField is on the lower half of the view. This means that when we start to type, the keyboard will appear and cover the TextField. That’s what happens by default. This is a bad user experience, especially on lower screens.

So, the question is how would we adjust the current layout, by moving the text field upwards in order to allow the users to see what they are typing and then, moving it back down to the original place when the keyboard dismisses? Let’s see how we handle keyboard animations and transitions in SwiftUI.

The main idea for this is that we will notify the view containing the text fields that the keyboard needs a space to display. Depending on the keyboard being displayed or not, we will update the padding-bottom of the view accordingly:

  • height is 0 if the keyboard is not displayed
  • height is equal to the height of the keyboard (depending on the height of the screen type) if the keyboard appears.

Okay, we have the idea, let’s get started.

2. Notification Center

Create a class and name it KeyboardResponder.  First of all, we will declare a notification center variable. Nothing’s new with this. We just listen to the events whenever the keyboard appears or hides.

private var notificationCenter: NotificationCenter

init(center: NotificationCenter = .default) {
   notificationCenter = center
   notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
   notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}

deinit {
   notificationCenter.removeObserver(self)
}

Here are the functions that need to be implemented for these events

@objc func keyBoardWillShow(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            // TO-DO: Update layout
        }
    }

@objc func keyBoardWillHide(notification: Notification) {
        // TO-DO: Update layout
    }

3. ObservedObject

In order to publish events, we need to do the following steps:

Step 1: Your class has to conform to ObservableObject protocol

final class KeyboardResponder: ObservableObject { }

Step 2: If you want to publish the change of any variable, declare the @Published prefix before its declaration

@Published private(set) var currentHeight: CGFloat = 0

This step is quite easy to understand. We just assign the corresponding expected values for it.

@objc func keyBoardWillShow(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            currentHeight = keyboardSize.height
        }
    }

    @objc func keyBoardWillHide(notification: Notification) {
        currentHeight = 0
    }

4. How to use observedObject

Back to the initial file, we declare a @ObservedObject variable like this

@ObservedObject private var keyboard = KeyboardResponder()

At the padding of our view (specifically here, it is a VStack), we just need to let the padding-bottom listen to the currentHeight from the keyboard. Whenever currentHeight has a new value, SwiftUI will receive it and update the UI immediately in a smooth and pleasant way.

NavigationView {
     VStack{...}.navigationBarTitle(Text(DataSource.firstUser.name), displayMode: .inline)
         .padding(.bottom, keyboard.currentHeight)
}

That’s it. No layoutIfNeeded(), no setNeedsLayout(), no any layout constraints stuff. And everything still works like a charm.

5. Improve UX

Besides taping done on the accessory view of the text field, we can also have another way to hide the keyboard by tapping outside of it. To do that, the easiest way is the following:

Step 1: Make an extension of View

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}

Step 2: Call it in onTapGesture function

NavigationView {...}
    .onTapGesture {
        self.endEditing(true)
    }

Conclusion

And that’s how we handle the keyboard in SwiftUI: hiding and showing it, adjusting the layout based on its status, as well as unfocusing it when tapping outside.

After reading this article, you’ll probably think: “Oh, how easy it is.” However, we believe that these small details might get us in a lot of trouble. And to save your time and effort, we will continue to write these types of articles. Stay tuned and we hope you like it. In addition, please let us know which parts of SwiftUI you are interested in the most or which parts of SwiftUI are giving you a lot of trouble via the comment below, or email us. We look forward to hearing from you.

Categories: SwiftUI

4 Comments

Ben · March 31, 2020 at 3:25 am

This handles the use case where the TextField is below the keyboard. However, what happens when the TextField is already above the keyboard. Wouldn’t this add unnecessary padding at the bottom, and might even remove the TextField form the view?

Kiran · March 31, 2020 at 6:15 am

Literally a life saving article, just got myself to start learning SwiftUI and was stuck on this topic. Thank you so much.

Briannix · April 2, 2020 at 9:53 am

Lovely Web page, Keep up the wonderful job. Many thanks.

Jamesskymn · April 28, 2020 at 11:28 pm

Fantastic Web page, Keep up the very good job. Regards.

Leave a Reply

Your email address will not be published. Required fields are marked *