Add Curved Text to Your Apps
Another week, another feature for our app. This week I had to make some research about how to add some curved text to our app.
I searched a lot and end up on different resources: from an official (but outdated) Apple example with CoreText, to a post on Stack Overflow written in Swift 2, 3, and 4. However, none of them offered a straightforward, easy to follow, mathematically sound approach.
I made several experiments, trials, and errors, before reaching this solution. I had to properly understand several concepts of how the text can be rendered on the screen and it could be useful to write them on (digital) paper for both the future me and anyone who could need it.
A caveat before starting: this article contains a bit of math, basic geometry, and some concepts of calculus, but nothing extremely complex. I tried to simplify them with graphs and to limit them to the minimum. Don’t be scared!
What we want is to draw some text, with custom fonts and colors, on a curved line, rotating the letters so that the result will look like this.
From a first investigation, this cannot be done out of the box in a
UILabel. To achieve that we need to perform some computations and we need to draw every letter one at a time, computing its position and rotation before drawing it.
The underlying maths is geometry. Let’s suppose that we want to draw our text over an arc of a circle.
The only thing we know is the
x-coordinate of all of our characters. What we need to compute are both:
y-coordinateof the letter;
- the rotation we need to imprint to the letter.
Luckily, both of them can be extracted pretty easily once we have the formula of the circle.
From our years in secondary school, we could remember that the equation of a circle is:
r is the radius of the circle while
y are its coordinates. So, let’s suppose for a while that we know the radius
r, we can invert the formula and obtain a function that, given a value
x, returns the correspondent
Now, how can we compute the radius? Well, this is something we can do with some considerations.
- When the curve is maximum, we can think that all our text stays on the full half of the circumference. Therefore, the
ris equal to half of the text length.
- If the curve is not at its maximum, we know that the distance for all the letters from the center must be the same.
- To keep the same distance, we need to move the center up or down by some value
C, so that all the letters are at the same distance. That means that our radius is growing, but how much?
- We can consider half of the text’s length as one side of a triangle rectangle while
Cis the other side. We can then compute the hypotenuse with Pitagoras theorem. The hypotenuse is our radius!
Basically, when the text is straight, we are rendering it using an infinitely big circle. The more the text is rounded, the smaller the circle, up to a circle whose radius is equal to half of the length of the text, given a font.
The code of the two functions we can use to compute the radius and the
y-coordinate are the following:
Now, given any
x-coordinate of a letter, we can compute the corresponding
y-coordinate passing the radius
x to the
Now that we have all these functions, we can compute the rotation of each letter. From trigonometry, we can remember that the rotation we have to imprint to the letter is defined by the angle created by the tangent of the letter.
At this point, we first need to compute the tangent. A mathematically perfect approach would require us to compute the first derivative of our function and ask for the value of the derivative in the middle point of the letter.
However, we can cheat a bit and simplify the computation, saving us from computing the derivative. In fact, we can consider every letter as a rectangle. We can use the
(xi, yi) of the leftmost point of the letter and the
(xf, yf) of the rightmost point. Now, we can compute the slope
m of the tangent with the formula:
The rotation angle is then simply
Drawing the Letters
We have all the relevant ingredients to draw our letters. But how can we do that? Apple provides us with a
UIGraphicsImageRenderer object that lets us draw something in a context and extract a
UIImage from it.
Then, to draw some text, we can leverage an API of
NSAttributedString that is
draw(at: CGPoint, with attributed: [NSAttribiutedString.Key: Any]). This method allows us to specify a point and to draw some text at that point.
Everything looks pretty fine: we know the
x and the
y, so we can draw the letter in the proper position, but… How can we rotate it? There is no API to rotate a letter!
Do not despair, my friend. If you can’t rotate the letter, we can rotate the paper! Or, in the UIKit world, the context on which we have to draw the letter.
CGContext offer some methods to rotate, translate, and scale the context itself. The important thing to remember is to save the state of the context before applying any transform. In this way, can restore it to the original state before drawing the next letter. This is extremely important because, otherwise, the transform would compose on each other and it will make really hard to reason about the coordinate system.
So… let’s put the drawing code together!
The code is a bit long, but it’s because I add a comment at every line to explain what is going on.
The most interesting part is the
for loop, where most of the computation takes place. In that loop, we compute
(x2, y2) to compute the slope
m. From the slope, we can compute the angle
theta for the rotation. We compute also
(xm, ym), that is the medium point of the letter. All of this happens between line 58 and line 73.
From line 81 to line 99, we move the context, centering it in the letter middle point, we apply the transform and we draw the letter.
The final result of this code is very well rendered by the following short video:
Here I’m playing with the
inflection parameter that is used through the whole code to compute the
y values. There are some other parameters you can customize: the kerning of the string, for example, can provide interesting results.
In today's article, we explored how to draw some curved text in our application. The approach we used is mathematically sound: it uses a couple of functions to deduct the position and rotation of the letters.
A very interesting side effect of this approach is that we can plug-in almost every continuous, derivable function in place of the half circle and we can be able to draw some text following any line we want!