A Stickler for Details: Implementing Sticky Input Field in iOS

iOS Messages app with sticky input field being dismissed interactively

When designing user interface for an app with an input field as primary focus of interaction (to accept and display text input), it’s a common pattern for the input field to stick (stay on the screen persistently). The input field attaches to the top of the keyboard and moves along with it.

Apple’s Messages is a great example for an app displaying the aforementioned UI pattern. Since iOS 7, the app also allows users to dismiss the keyboard interactively while dragging down upon the scrollable view.

This pattern can be seen in most if not all, chat apps. Few have managed to execute it as well as Apple’s Messages app, notable examples being Facebook Messenger and WhatsApp.

It’s not that big of a deal, yet it ain’t easy

Being able to dismiss the keyboard interactively is not really a big deal, it’s probably a small detail that most users wouldn’t even notice. But, it certainly adds a nice touch to the app.

However, It’s unfortunate that the implementation is far from obvious. Developers have tried many approaches with varying degrees of success. There are few open source projects, blog posts and StackOverflow entries related to this subject, offering solutions ranging from simple to painfully complex.

In short, implementing sticky input field is quite trivial. But, it gets tricky when we want to allow users to dismiss the keyboard interactively. Here’s how some popular chat apps fare:

Google Hangouts: The input field moves and overlaps with the keyboard when dismissing interactively. Slack: There is a gap between the keyboard and the input bar during the dismissal animation.
Slack for iPad: It does not support interactive keyboard dismissal even though it’s supported on the iPhone.
Telegram, Line, WeChat: They do not support interactive keyboard dismissal.

Gathering the ingredients

While working on a solution, I came across blog posts detailing very promising technique that actually brings us halfway to the solution: returning non-nil for UIViewController root view’s inputAccessoryView and making it first responder on load. You can read about it here: https://robots.thoughtbot.com/input-accessorizing-uiviewcontroller and http://derpturkey.com/uitextfield-docked-like-ios-messenger/.

However, It has at least 2 caveats:

  • The input field is subview of the keyboard’s window. This is likely undesirable, especially for chat applications.
  • The input field’s width will extend the width of the keyboard. This breaks split-view use case on the iPad.

Baking a solution

We already know that returning non-nil for inputAccessoryView gives the effect we want, but with some caveats.

So, what if:

  • We use an invisible view for the inputAccessoryView
  • And in order to know about the keyboard’s frame changes (as the result of interactive dismissal), we use KVO (Key-Value Observing) to observe the bounds/center property of the invisible view’s superview (i.e. the keyboard)?

Putting it all together

Design diagram for the solution

The diagram shows key components for the solution based on the idea described earlier. Here’s we have labeled the invisible view as “Pseudo Input Accessory View”.

  • The heart of the solution is an instance of the Keyboard Tracker. It tracks and stores keyboard states from 2 sources: UIKit keyboard notifications and pseudo inputAccessoryView callbacks for its superview bounds/center changes.
  • Pseudo Input Accessory View Coordinator manages single instance of pseudo input accessory view and also provides method to change height of the managed input accessory view (to support requirement of growing input field).
  • Pseudo Input Accessory View is created and managed by its coordinator. It tracks its superview bounds/center property using KVO and reports the changes to its coordinator.

Based on this design, the app is able to query current keyboard states at any point of time via the keyboard tracker instance. And realtime keyboard updates are available via the tracker’s delegate callbacks.

Using the keyboard’s current frame information, we adjust the layout of the input field so that it ‘sticks’ with the keyboard. In the tracker’s delegate callbacks, we just need to re-adjust the layout as necessary.

Does it work?

Yes, I think we’ve nailed it!

Pie for iPad with sticky input field

Above is a demo of Pie for iPad with the sticky input field solution we’ve implemented. (sorry for the shameless plug 😀😁🙌👏🙏🎉).

Open sourcing the solution

At Pie, we strive to build the best chat experience for work. But aside from that, we also love to give back to the community. Therefore, we’ve packaged the solution with an example app as a small open-source library.

We’re hoping that it will be useful for other developers working on similar requirement. If you’re interested, feel free to check it out at https://github.com/meiwin/NgKeyboardTracker.

Thanks for reading this post. If you enjoyed it, press the recommend button and hit retweet. 🙏🎉