Avoiding keyboard in SwiftUI

Michał Ziobro
Mac O’Clock
Published in
4 min readMay 4, 2020
Photo by Aryan Dhiman on Unsplash

In this tutorial I would like to demonstrate approach do implement keyboard avoidance in SwiftUI framework.

As you already know you can implement keyboard avoidance for UITextFields in UIKit using two notifications from NotificationCenter i.e.

  • UIResponder.keyboardWillShowNotification
  • UIResponder.keyboardWillHideNotification

The same notification will be used in this tutorial to implement KeyboardAvoider object and reusable KeyboardAvoiding component to wrap SwiftUI forms and views and empower them with easy to use, copy-paste solution.

1. Using KeyboardAvoider component in SwiftUI

Generally we can have two use cases of component we are going to build. Firstly want to use it with static views and secondly we want to use it with scrollable views like List, ScrollView.

1.1 Using KeyboardAvoider with static views

Let’s start by looking at code snippet

As you can see it is just simple SwiftUI View that has VStack with two TextFields, and Button. TextFields are placed near the bottom of screen to enable testing keyboard avoidance while it will show and hide.

Firstly, we inject @ObservedObject of KeyboardAvoider as properties of the view.

Then we wrap entire view (where keyboard avoiding will be happening) with special component KeyboardAvoiding passing to it as it’s single argument avoider object.

Lastly, we need to indicate for each TextFields (or other views) that we want them to avoid appearing keyboard. To do this we use special modifiers .avoidKeyboard(tag: Int). As you can see we can place it directly on TextField as in the case of “Field 1” or we can place it on any view like Button. The last case is useful if we want to move up entire form including Submit buttons while keyboard is appearing on the screen.

Each time user starts to edit TextField and we want to indicate which view/textfield should be now taken into account by KeyboardAvoider as interfering with appearing keyboard we use assignment

self.avoider.editingField = 2

Here it was added in onEditingChanged closures of TextFields. Assigned number should correspond to previously assigned tags.

1.2 Using KeyboardAvoider with scrollable content (List, ScrollView)

In the case of TextFields and views placed inside List or ScrollView we can do something like this.

Here we also have two TextFields placed at the bottom of the List.

Similarly to previous use case we inject @ObservableObject of KeyboardAvoider.

The main difference is that we aren’t using KeyboardAvoiding component as here keyboard avoidance is implemented as modifying bottom padding of List/ScrollView. We also do not need to use avoidKeyboard(tag: Int) modifier and set currently focused editingField on avoider.

In above example one TextField doesn’t use this avoidKeyboard/editingField pair, and other is using it. Using this modifier enables as to detect obstruction of that TextField by keyboard and implementing some additional logic that is extra to what keyboard avoiding is doing. In my own app I’ve used this two add additional offset for currently focused field. Important! It requires registering keyboard avoider via .registerKeyboardAvoider().

The most important thing to make keyboard avoiding work for scrollable content is addition of modifier .attachKeyboardAvoider(avoider, offset: 32)

As you can see you can pass optional offset. This offset enable us to adjust List bottom padding while keyboard is avoided as there can be many different scenarios like view inside NavigationView or TabView or both, and this adjustment is needed to take this differences into account.

2. KeyboardAvoiding component

Now let’s look at KeyboardAvoiding component

As you can see this is generic SwiftUI view that is wrapping internal view and just applying some modifiers. To enable to pass any content inside this KeyboardAvoiding view we use @ViewBuilder.

On content view we apply modifiers that adjust offset of this view depending on values produced by KeyboardAvoider observed object. We also set some animation and .registerKeyboardAvoider()

What is keyboard avoider registration?

It is just preferences observation. Discussing PreferenceKeys is outside the scope of this tutorial. So if you are not familiar with them just read this article. In short this preferences are used to propagate textfields bounds rectangles up view hierarchy and registering them in KeyboardAvoider object. There will be used to check whether this rectangles intersect with keyboard bounds rectangle.

3. avoidKeyboard() modifier and KeyboardAvoiderPreference

So now we can look how avoidKeyboard(tag: ) modifier we attach to TextFields (or other views) is implemented.

As you can see it just assigns modifier KeybaordAvoiderPreferenceReader.

This preference reader reads given view bounds and propagate them up using SwiftUI Preferences mechanism.

At the end of this section we can look at preference and preference key implementation. There is nothing fancy if you ever used Preference in SwiftUI.

4. attachKeyboardAvoider List/ScrollView modifier

In the case of scrollable content we are using attachKeyboardAvoider() modifier. There is also .attachKeyboardAvoiderPublisher() version of this modifier you can find it in GitHub code. But we won’t discuss it here.

As you can see in the above code it just calculates adjusted keyboard avoidance offset and set it on List or ScrollView as its bottom padding.

And here is the code that calculates adjusted keybaordOffset based on NavigationBars, TabBars and custom offset specified in client code.

5. KeyboardAvoider — the engine of keyboard avoidance

In this last section of tutorial we look at keyboard avoidance core implementation class. It is ObservableObject that you’ve already seen injected in multiple places above.

KeyboardAvoider has multiple properties

  • dictionary storing rectangles of views bounds, it is tag to CGRect mapping.
  • rects can be updated only if keyboard is hidden so we have additional public property and check of keyboardRect against .zero
  • editingField is tracing currently focused text field/view that we want to be avoiding keyboard
  • slideSize and slideSizePublisher are publishing current offset required to successfully avoid appearing keyboard
  • we also have references to two cancellable publisher keyboardWillShow and keyboardWillHide

In initialiser we setup this two publisher of keyboard showing and keyboard hiding (this are the same notifications you’ve already used to in UIKit). At the end we use them to update current keyboardRect and then update slideSize.

Where to go from here?

Full code with KeyboardAvoider solution you can find on my GitHub account

--

--