Last week I published my first open source library, QuickTicker. It’s a Swift library that lets you create simple ticker animations using one line of code. The result looks like this:
In this post, I’d like to talk about this project and cover:
- Why I created this library
- How I built it (the coding part)
- Final details (example project, unit tests, README.md, Cocoapods)
- Summary of major points and general advice (aka TLDR)
Why open source?
Starting with the obvious question, the reason I decided to build this library was because this is a functionality that I usually end up incorporating into most of my projects anyway; so I figured I might as well make it a bit more generic and package it up into a library that can be easily added to any project, as opposed to copy & pasting code between projects.
Building this library was also an opportunity for me to practice thinking in terms of APIs and practice building modular code while hiding implementation details. I also had to use generics, which I hadn’t used in any of my previous projects!
What does it do?
When I started this project, my goal was to build a simple library that lets you animate labels similar to the gif above. I ended up adding a few additional features over the course of the project, although the core concept is still the same. Here is the list of features I ended up with:
- Start an animation using just one line of code
- Familiar syntax to anyone who’s used UIView’s animate methods
- Accepts any Numeric value as end value, you don’t have to convert or typecast
- It works even if there is text mixed in with numbers in the same label. Text remains intact while the digits get animated 👍
- Completion handler lets you safely queue-up actions following the animation
- You can optionally specify animation curves and decimal points for the label
- Works on both UILabel and UITextField (I intend to expand this later)
Label animation, the main purpose of this library, is something I’d learned about from a youtube video by Brian Voong. In the video, Brian talks about CADisplayLink and how you can use it to animate text in UILabels to create counters and other similar effects. There is some boilerplate code required to get
CADisplayLinkworking (including selectors and @objc methods), and that’s where I think this library can be useful.
Before I go on, I’d like to mention a talk given at Swift & Fika 2018 by Daniel Kennett about API design. During that talk, Daniel spoke about the API boundary, which is the line between the code of the API and the code of the user. As an API designer, you get to choose where this boundary goes, and this decision can have great ramifications.
The closer the boundary gets to the user code, the more work you have to do as an API designer. In return, the API becomes much easier for the user to implement (think Crashlytics with their ridiculously simple install process). Daniel showed this image to convey that point:
On the other hand, if you decide to place the boundary much closer to your code, you end up having to write less code, but you give the user much more work to do. In return, they usually end up having more control over the API. Daniel offered the example of Spotify Metadata:
I don’t think either approach is wrong. It all depends on what you’re trying to accomplish with your API. In my case with QuickTicker, my goal was to make it as simple as possible for the user to get started, ideally a one-line function call. So I was interested in making something more similar to the first image (Crashlytics).
How I built it (the technical part)
The early versions of the library did not use a dedicated type. Instead, I built it as an extension on the UILabel type (you can still see it in the early commits on github if you’re curious what it looked like). So you would call the API using dot syntax directly on your label, something like this:
let someLabel = UILabel()someLabel.startTicker(duration: 2, endValue: 250)
There were a few things I didn’t like about this approach. I wasn’t a big fan of extending the entire UILabel type, as I didn’t want to pollute the namespace for all UILabels for the user, when they probably only needed to animate one or two labels. This approach also meant that I couldn’t extend the same functionality to other types like UITextField without duplicating all my code.
As an aside, during this early experiment with type extension, I learned that it is actually possible to add stored parameters to type extensions in Swift. To do this, you’d have to define a computed property as an associated object, then access that object using an association key, which is a unique pointer to that association. The end result looks like this:
Sure the syntax is ugly, and very “unswifty”, but it gets the job done, and lets you do something that is otherwise not possible in Swift. I think it’s really cool!
Anyway, back to the main topic 😁 as I mentioned earlier, I ended up scrapping this type extension approach for my API, and I went with a dedicated type. Two types to be precise. One of them publicly available, one internal (not visible to the user).
Now you might be wondering why I didn’t go with a protocol, as it may seem like a good solution here. The problem is that I needed to store properties (you’ll find out why when I discuss the implementation below), and protocols don’t allow that (as far as I know!). They only let you add computed properties, and the trick I mentioned above with associated objects doesn’t work on protocols.
This brings us to my custom types. One of them is a public class named QuickTicker, which contains three class methods. The first method contains all possible parameters, while the other two are convenience methods that call into the main one with some default values. The syntax for these methods is inspired by UIView’s animate methods, which will be familiar to many users.
I could have provided default values to all parameters on the main method instead of writing 3 methods, but that didn’t give me the desired level of granularity when testing out auto-complete options. Another advantage to writing separate methods is you get to specify the auto-complete tooltip text for each one, explaining the default values to the user (e.g. 2 second duration). The auto-complete tooltip by the way is added via /// (3 forward slashes) above the method.
I used the class keyword instead of static for these methods, because I wanted to give the user the option to override them if they wanted to. This class also contains an Options enum. The options include 3 animation curves, as well as a decimalPoints case with an associated value denoting the number of decimals. This options array is a nice way to future-proof the API, as it lets me safely add further options down the line without breaking existing users’ code.
The main method accepts two generic parameters, one for the end value, which could be any Numeric value, and one for the animated label, which (for now) could be a UILabel or a UITextField. It looks like this:
It took me a while to figure out how I could create a function that accepts any Numeric value in a manner that is transparent to the user (thinking of the API boundary here). After some research online, I ended up using this answer on StackOverflow. This answer was posted before the Numeric protocol was made official in Swift, so at first I tried to adapt the answer to the existing Numeric protocol in the language, but after several unsuccessful attempts I ended up creating a NumericValue protocol that worked out beautifully (if you know of a way to make this work with Numeric, I would love to know how!)
Next we have the internal QTObject class. This class is necessary because I want to store several values over the course of the animation. Most notably, a weak reference to the animated label, the end value, a completion handler, a reference to the ongoing CADisplayLink (in order to stop it), and other options.
This object has an interesting method called getStartingValue(from: animationLabel):
As I mentioned in the list of features, QuickTicker updates the digits in your label while leaving the text intact. In other words, if you feed it a UILabel with the text “Temperature: 13F”, only the 13 part will get animated. This contributes to placing the boundary closer to the user’s code (less work for the user, no need to worry about mixing text with digits, but more code in the API to handle different scenarios).
To achieve this, the method above works like this:
- Try to convert to Double (using Double’s failable initializer)
- If it succeeds, return the digit (there is no text in the label)
- If it fails (there is text in the label), then find out where the digit starts and ends within the text
- Convert the value behind that start…end range to Double, that’s your starting value (the rest is either text, or a second digit, but we’re only concerned with the first digit we find)
To accomplish the above, I make use of another method called getFirstAndLastDigitIndexes. This one is pretty big, so I’m not gonna paste it in the article (you can see it here), but essentially what it does is define an NSCharacterSet with decimalDigits plus “.” then loop over the text looking for any matching characters (while accounting for edge cases, like a “.” in the middle of text)
Moving on to the QTObject initializer, when this gets called (by the class methods in QuickTicker), and once all the properties are initialized (including the starting value mentioned above), the CADisplayLink gets added to the default mode run loop, thereby starting the animation.
Small note regarding run loops: I intend to switch to common mode run loop in my next release, as I just learned that default mode can get blocked by touch events (i.e. scrolling your finger across the screen).
The CADisplayLink is tied to the device refresh rate, and is guaranteed to call its selector (the handleUpdate method in our case) on every screen refresh (every 16.7ms on a 60hz device); this gives you the entire 16.7ms duration to execute that method. Generally speaking, if you have CPU intensive code and wish to take advantage of that entire 16.7ms duration it’s better to use a CADisplayLink than a Timer, since timers can fire up slightly ahead or behind their intended time, thereby giving you less time to execute your code.
Next up is handleUpdateFor:
On every frame update, the handleUpdate method gets called. This method will check the elapsed time since the animation has started, and will calculate the current value for the label based on:
- The percentage of elapsed time / full duration
- Animation curve stated by the user (default is linear)
- End value stated by the user
- Number of decimals stated by the user
This is the crux of how the label animation works. Every frame update, you calculate how much time has passed since the animation has started, compare that against the full duration of the animation, and update the label accordingly. If we reached max duration, the displayLink is invalidated, and the completion handler is called.
For example, let’s say you wanted to animate a label from 0 to 40 over the course of 2 seconds. After 0.5 second has elapsed, you are 25% into the animation. With a linear curve, that means the label must show 25% of 40, i.e. 10 at that point in time.
There are a few interesting methods called over the course of the above process. First up is
This will return a value based on the curve stated by the user. The easeIn curve is easy to calculate, since the percentage is a range from 0–1, so it’s just a matter of raising that percentage to the power of 2.8 (an arbitrary number I chose through trial and error to get a nice curve). To get the inverse curve (easeOut), I subtract the percentage from 1 before raising it to the power of 2.8, then subtract from 1 again.
The next method I wish to explore is updateLabel:
This will apply the desired number of decimal places to the calculated value before updating the label. The user can request a specific number of decimal places, such as 5 zeroes. If they don’t specify anything, the number will be inferred from the requested end value (e.g. if you specify 157.83 as the animation end value, then 2 decimal places will be maintained throughout the animation.
By default, Swift will discard extra zeroes after the decimal point. So if you enter something like
let x = 0.983010000 it will be converted to
0.98301 and we wouldn’t be able to respect the desired decimal places. To get around this, we call a function called
padValueWithDecimalsIfNeeded that looks like this:
This method does the following:
- Infer the number of decimal places from the calculated value (at that point in the animation)
- Compare it against the requested number of decimals
- If we are short (i.e. Swift discarded extra zeroes), then we pad it out to match the requested number
The second method used inside updateLabel is updateDigitsWhileKeepingText, which ensures that only the digits in the label get animated; the rest of the text stays intact. (just as we did in the beginning to infer the starting value)
This pretty much covers all the major parts of how the library works. There’s a total of 2 classes and 2 protocols, but I placed all of them in a single QuickTicker.swift file to make installation easier (just drag & drop one file). I like it when libraries let you do that (e.g. SwiftyJSON)
I decided to create an example project to showcase the library. This can be useful to generate some screenshots for the readme, and also to demonstrate to the user how the API calls work with some examples.
I personally find example apps really useful. For instance, when I implemented Google’s Cloud Firestore realtime database in one of my projects, the example helped me out a lot, especially to get acquainted with the API calls. I kept repeatedly referring back to the example app while working on my project to check how certain things are done.
For QuickTicker, I ended up building a single-page app with some sliders to let the user experiment with the different settings, as well as some pre-set counters at the bottom. It looks like this:
Some lessons learned while building the example app:
- If you intend to create a Cocoapod (more on that below), don’t build an example app manually. Let Cocoapods create it for you, then change it as you please. You’ll thank me later! 😃
- Use UIFont.monospacedDigitSystemFont for animated labels to prevent wobbling during the animation (especially when combining text with digits)
This was an interesting one. I have limited experience writing unit tests; it’s definitely an area I need to work on 😄
When I started writing tests for this library, I quickly learned two things:
- In your tests file, you need to add a @testable keyword before your import statement in order to access internal classes
- You cannot access any private or fileprivate methods from your tests, even with @testable
The second one was pretty big for me. At that point, all of the classes and methods I had written were private except for the 3 class methods that I exposed to the user. I wanted to expose only the bare minimum so I wouldn’t pollute the user’s namespace with meaningless things like QTObject.
But in order to test the methods inside QTObject, I had to remove the private restriction, including the initializer for QTObject. I added a comment to the initializer, alerting the user not to call it directly and to use the class methods instead. And later on, I found out that since this is an internal class, if you install QuickTicker via Cocoapods, you can’t access it directly anyway. Perfect!
Here are the tests I wrote if you’re curious. Not comprehensive by any means, but still serviceable I think.
How to build a README.md
In the same excellent Swift & Fika 2018 conference I mentioned earlier, Roy Marmelstein gave a talk about open source in which he emphasized the importance of having a good readme page for your project on Github.
A good readme starts by explaining what the library is, and what you can do with it. Hopefully it’s something that’s usually hard but is easy with this library. Roy recommends creating a custom logo, and adding screenshots and especially gifs if applicable to your library (i.e. if it’s UI-related).
Other important sections include installation instructions and requirements, as well as an example project.
I started with a readme template, then I used these repos for inspiration:
Next up was Cocoapods. I have used Cocoapods extensively in my projects before, but never actually created one. The process ended up being surprisingly straightforward.
One advice I’d like to reiterate here, is if you’re planning to include an example project in your library, let Cocoapods create the initial version (along with the test files), then modify it as you please. I had created the project myself, and then when I built the Cocoapod I ended with a second project, so I had to manually move files around from the old project to the new one. It’s a bit of a hassle.
I think I’ve covered all the major steps I went through while building this library! This was a fun weekend project, and I certainly learned a lot while doing it. To summarize some of the important points + some general advice:
- If there is a certain functionality that you keep adding to your projects over and over, consider packaging it up into an open source library
- Open source helps you build modular code and think in terms of APIs
- Before you start, consider where you want to place the API boundary
- If you place the boundary close to the user’s code (like I did with QuickTicker), expect to do more work on your side
- Try to future-proof your design, and plan for additive changes in the future as opposed to breaking user’s code in future updates (e.g. I added an options array that I can safely expand without breaking any existing code)
- If you intend to add an example project, and you wish to create a Cocoapod, let Cocoapods create the project + tests file for you
- Including an example project helps you generate screenshot (if applicable), and it helps introduce the users to your API
- Don’t forget about Unit Tests! Libraries with no tests get a lower CocoaPods Quality score
- And finally, having a good README.md file can be vital for the success of your library!
Hopefully, this article has inspired you to create your own open source library and share it with the world! Thanks for reading!
If you have any questions or suggestions, please leave a comment or tweet @BesherMaleh
You can find QuickTicker here.
If you’re curious, here are a couple of my apps that use this library