When creating an app that requires information from a user, chances are you use one or more textfields. If we have multiple fields embedded in a scroll view, we want to ensure that the contents of the scroll view do not become inaccessible when the system keyboard is displayed for the user to fill out a field. In this post, we investigate how to handle keyboard presentations in a modular way.
Keyboard Handling the Regular way
To handle keyboard presentation, iOS provides a set of notifications:
UIResponder.keyboardWillShowNotification=> right before keyboard presentation
UIResponder.keyboardDidShowNotification=> right after keyboard presentation
UIResponder.keyboardWillHideNotification=> right before keyboard dismissal
UIResponder.keyboardDidHideNotification=> right after keyboard dismissal
Each of these notifications has information in the
userInfo that gives us information
about the system keyboard:
UIResponder.keyboardFrameBeginUserInfoKey=> Start frame of the keyboard before it is presented
UIResponder.keyboardFrameEndUserInfoKey=> Final frame of the keyboard after it has been presented
UIResponder.keyboardAnimationDurationUserInfoKey=> Duration of the the keyboard animation
Let’s say we have a table view that has some textfields. When a textfield is active, and the keyboard is presented, we want to ensure that the user can still scroll to the bottom of the table view without the keyboard obstructing any of the content. The regular way to do this will be as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
This works, but it isn’t very reusable. If we had another view controller that needed to adjust it’s content due to keyboard presentation, we will have to duplicate this logic all over again. The good news is that we can do better!
Keyboard handling as a behaviour
Composition is a way of extending the functionality of a class by delegating behaviour to one of it’s children. If we consider how a View Controller handles keyboard presentation as a behaviour, it enables us to implement different behaviours, and inject said behaviours into the View Controller based on what functionality we want. Here are some examples of possible behaviours:
- Adjusting scroll view content inset to account for keyboard presentation.
- changing the bottom anchor of the view to account for the keyboard presentation.
- Offseting the frame of all views on the screen by the presented keyboard size.
All of the above are behaviours that a view controller can perform when a keyboard is presented. The goal is to be able to reuse any of these behaviours whenever we want. In order to achieve this, we will break down the problem of handling keyboard presentation into two parts:
- Observing when a keyboard is presented, and extracting the necessary information about the keyboard presentation
- Behaving in a particular way to account for keyboard presentation using the information obtained from (1)
Observing keyboard presentations modularly
We want to create an abstraction that listens to keyboard notifications, and informs observers about the event. Our abstraction will only be able to inform a single observer via closures, but this can be easily extended to informing multiple observers. Consider the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
In the code above we create a
KeyboardDisplayInfo that holds all the information (contained in the
notification object) about the keyboard to be presented. We then listen to all the keyboard related notifications, invoke the respective closures supplied by the observer. This
KeyboardObserver class is the foundation that we will use for creating various keyboard behaviours. In this post, we will use this to implement the first of the aforementioned keyboard behaviours. Before we begin, let’s first define a protocol that all behaviours will need to conform to:
This behaviour adjusts the supplied scroll view content inset to account for keyboard presentation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Believe it or not, this behaviour matches the behaviour we defined in our initial
TableViewController code. We can now update our
TableViewController code to use this behaviour as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Notice how the view controller is no more concerned with listening for keyboard notifications. By abstracting the keyboard adjustment into a behaviour, the view controller can just declare the kind of adjustment behaviour it wants, and the rest gets handled. This makes the code very modular, as we can easily replace the behaviour with a different behaviour without having to change the view controller code. It also supports reuse as this behaviour can easily injected into any view controller that has a scroll view.
By using composition, we were able to create a modular keyboard adjustment behaviour that changes the scroll view’s content inset to account for the presented keyboard. This provides an isolated abstraction that can be easily tested, and reused in many view controllers without duplicating code. Composition enables us to abstract a lot of functionality as behaviours that can be easily injected into view controllers. See my post on resizable formsheets for another example of this.
Thanks for taking the time 🙏🏿