Introducing Placeholders

Set and animate multiple placeholders on UITextField

Oleg Dreyman
AnySuggestion

--

Placeholder is a powerful concept with a long history — it’s easily recognizable by users, has a strict, reasonable meaning and gives the developer an easy way to hint their users.

Sometimes, however, having just one placeholder is not enough. Sometimes you want to demonstrate your user all the variety of the content that can be entered. For example, if you have a field named “Activity Title”, you can, of course, just put “Running” there as a placeholder. Or you can define a set of placeholders — for example, “Running”, “Walking”, “Rowing”, “Boxing” — and animate them. If this technique is used carefully, it can provide a great user experience. It looks like this:

Basic “push from bottom” transition

Placeholders library allows you just that. Actually, it’s really really simple, and I encourage you to try to implement something like this yourself — it could become a good introduction into iterators, CATransition and Timer (formerly NSTimer). In this article, we’re going to take a quick look at the API, and then we’re going to discuss some architectural choices and features.

Before we begin, here’s the Placeholders on GitHub:

Now let’s dive right in!

How to use

Placeholders was designed to work nicely with view controllers. The central piece of the API is a Placeholders class. You create it in a straightforward manner:

let placeholders = Placeholders(placeholders: ["Running", "Walking", "Biking"])

This will create a set of three placeholders. Of course, often you want to “loop” your placeholders, so that the animation runs forever. Or you want to shuffle your set, so that users don’t see the same placeholders in the same order every time. For that, you should use .infinite and .shuffled options, respectively, like this:

let placeholders = Placeholders(placeholders: ["Running", "Walking", "Biking"], options: [.infinite, .shuffle])

And then, in the viewWillAppear method you should bind the Placeholder instance to your UITextField. You do that by calling start method:

placeholders.start(interval: 3.0,
fireInitial: true,
textField: textField,
animation: .pushTransition(.fromBottom))

(fireInitial is used to set the first placeholder instantly)

And that’s basically it! Now you have multiple animated placeholders. You can play with an interval or tune your animation to fit your needs. If you want to create a completely customized animation, check out the README.

How it works

Overall, the solution is pretty simple: it’s a combination of Timer, iterators and CATransition. The data source is provided via AnyIterator, the placeholder changes are scheduled with Timer and performed with simple CATransition.

If you want to know how the animation is performed, here it is:

let transition = CATransition()
transition.duration = 0.35
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCATransitionFromBottom
textField.subviews.first(where: { NSStringFromClass(type(of: $0)) == "UITextFieldLabel" })?.layer.add(transition, forKey: nil)
textField.placeholder = nextPlaceholder

(Disclaimer: that’s not the code directly from the library — the actual code is split into several functions, but for the sake of simplicity I put it all in one place here.)

How it was made

While developing Placeholder, there were a couple of interesting architectural decisions to make. I want to share some of these in this article.

1. Composition instead of inheritance

Often this kind of added functionality is done via subclassing. Of course, it is possible to simply create MultiplePlaceholdersTextField class and to add some nicely-looking setPlaceholders method to it. But, however, it makes all your custom subclasses of UITextField unusable (or, what’s more likely, you simply won’t use this library). It also decreases the reusability of the code and, overall, is a shady architectural decision: this logic simply doesn’t belong to the View layer.

So that’s why Placeholders is a separate object, which is located in a Controller layer (where, as I believe, it belongs) and can be used with any UITextField, so it’s easy to integrate even with existing codebases.

2. Isolated implementation

If you look at the Placeholder class, you’ll see that it knows absolutely nothing about UITextField (it doesn’t even import UIKit). And all the UITextField functionality is put in an extension, which enables an unprecedented level of customization. Basically, you can customize the process almost at every step.

The Placeholder object itself has only one start method:

func start(interval: TimeInterval, fireInitial: Bool, action: @escaping (Element) -> ())

And the method for UITextField which is described in the usage section is just a wrapper around it. That efficiently separates the logic, making the actual implementation simpler and more manageable.

You can read more about the idea behind this approach here.

3. Iterator instead of array

Under the hood, Placeholders uses AnyIterator instead of a simple array. This is made for two reasons:

  1. Having iterator is enough. We don’t need the fully equiped array here. We just need something that will give us the next element each time. Iterator is actually that “something”.
  2. Iterators can be modified in interesting ways. For example, the “infinite” functionality works on an iterator level, it’s not something that’s embedded into Placeholders. You can check out the implementation of InfiniteIterator here.

4. Generics

You may have not noticed it, but Placeholders is actually a generic type. Why is that? Because this way you can use both String and NSAttributedString with an identical syntax!

Well, that’s the Placeholders. It’s a small and simple library, but it was fun and interesting to write. If you have any questions about it (or about anything else) — ask me a question in “Responses” section below, or ping me on Twitter. Or you can also open an issue (or a pull request!) on GitHub:

Thank you for reading!

--

--

Oleg Dreyman
AnySuggestion

iOS development know-it-all. Talk to me about Swift, coffee, photography & motorsports