Keyboard access in iOS: cutting back on copypasting
In almost any mobile app, developers encounter input fields. And where there’s an input field, there’s a keyboard, and logic to process events of its lifecycle: emerging, hiding, resizing.
Anyone who’s ever written apps for iOS, knows that writing code for keyboards involves adding pretty similar or even identical code, otherwise known as copypasting. To find out how we were trying to tackle it at Surf and how much we managed to reduce the code base by, keep reading.
Suppose we have a screen with an input field. If you tap it, it shows the keyboard. What we have to do is process the events of showing and hiding a keyboard depending on its dimensions and show/hide time. The code may look as follows:
Suppose we now have another screen with input fields. No biggie: let’s copy the methods we’ve already written and change the way the events are processed.
Then we have another screen, and another one… I guess you know what I’m getting at: all screens will have exactly the same code that only differs in the way the events are processed. The developer is highly unlikely to write this code from scratch instead of copypasting with Cmd+C/Cmd+V.
It clearly means that it’s high time we consider code reuse to avoid copypasting. That’s how we got the idea to write a utility which would:
- Help subscribe to keyboard events and unsubscribe from them.
- Call a previously specified method on the events of showing and hiding the keyboard.
- In doing so, pass data of specific type (animationDuration, keyboardFrame and so on) instead of a raw Notification object.
- Relieve NotificationCenter, sparing you the pain of trying to remember the right keys and data types every time.
- Help avoid copypasting.
How we made the utility
The first question that comes between you and clean code is: how do I arrange it in terms of architecture? We want to take processing of keyboard events outside the ViewController, but we’ll need to call its methods. That brings up some questions:
- Where do we process the events then?
- How do we bind ViewController and the object where the events are processed?
- How do we keep the strong-link to this object stored somewhere?
Base classes won’t do. We want to make the system flexible, and base classes won’t help it.
To outline the first version of this utility, we use a diagram and a short listing below:
public protocol KeyboardObservable: class {func subscribeOnKeyboardNotifications()func unsubscribeFromKeyboardNotifications()func keyboardWillBeShown(notification: Notification)func keyboardWillBeHidden(notification: Notification)}
Here’s the basic idea:
- We specify that ViewController complies with the KeyboardObservable protocol.
- The protocol contains 4 methods. Two of them — subscribe and unsubscribe — are implemented in the default extension of this protocol, so there’s no need to implement them.
- While we are subscribing to notifications, we create an object called observer, containing a weak reference to ViewController. This one would be responsible for processing the events of showing and hiding the keyboard: it’s these exact methods that would be called in response to notifications.
- Whenever the keyboard is being shown or hidden, observer will call the two corresponding methods from view.
Which partly solves the problem: now, we don’t need to implement subscriptions to notifications, since all the same logic will be implemented in one place. But we still have to do something to process the Notification object and draw all the necessary parameters out of it — i.e., we need to implement the remaining two methods in the KeyboardObservable protocol.
To do that, we’ve made provision for the protocols specified in the diagram as <Specific>KeyboardPresentable. They may look as follows:
public protocol CommonKeyboardPresentable: class {func keyboardWillBeShown(keyboardHeight: CGFloat, duration: TimeInterval)func keyboardWillBeHidden(duration: TimeInterval)}
To use the protocol, we need to specify that, besides KeyboardObservable, ViewController also complies with the CommonKeyboardPresentable protocol as well as implement its methods.
CommonKeyboardPresentable has an extension where the remaining two methods of the KeyboardObservable protocol are implemented. When they are called, the necessary parameters are extracted from the Notification object and the corresponding CommonKeyboardPresentablе methods are called.
We no longer need to copypaste the logic of processing the payload from the notifications — it will all be implemented in one place. That said, we can still expand the mechanism and write our own <Specific>KeyboardPresentable, where the methods have parameters customized to our needs.
Particular attention should be given to storing observer in memory. In the diagram, its storage is marked as Pool.
- Pool is an observer storage that keeps a strong reference to each of them and doesn’t let them leave the memory.
- Each observer has a weak reference to the ViewController, for which it was created.
- That way, we’ve managed to avoid the reference-cycle between ViewController and the corresponding observer.
- We’re still left with “stray” observers, when an observer object contains view == nil. It’s a case where a ViewController has passed away leaving the observer alone. You can address this by occasionally cleaning such objects out of the pool.
As a result, we have a flexible system that doesn’t require keeping a large number of constants in mind and writing large chunks of repetitive code. All you have to do is:
- Declare that the ViewController supports the KeyboardObservable protocol.
- Fix the errors that have occurred in Xcode by implementing two methods of the protocol.
- Or declare that the ViewController supports the SpecificKeyboardPresentable protocol and implement its methods.
The class may have the following structure:
That said, you can use the protocols available in the utility as SpecificKeyboardPresentable (e.g., CommonKeyboardPresentable), or write your own protocol. All it needs to do is comply with the KeyboardObservable protocol and implement two methods absent from the default implementation.
As a little extra something, here’s KeyboardInfo, a struct that makes it easier to work with the userInfo dictionary in notifications:
Outcome: we’re down by hundreds of absolutely identical lines of code
Thanks to this utility, the amount of code within one screen hasn’t gone down drastically: about 15 lines less. But in apps with numerous screens we’ve deleted around 1000 absolutely identical lines of code!
And the best thing is: now, you don’t need to try and remember the keys for Notification every single time. You don’t even have to remember the names of the methods in protocols: Xcode will suggest declaring the missing methods for you. All you have to do is add the implementation.
To take a look at the entire code of this utility among the others, check out the Surf repository.