We all know that duplicating code is a bad practice. We have to avoid repetitive logic that results in expensive fixes in case a project requirements change. Cost of fixing issues caused by code duplication only increase as the project grows.
Problems caused by duplication are commonly understood when we write code, but we usually don’t pay attention to duplication when we build UIs. But duplicating while creating a UI is similar to duplicating in any other context.
For example, you might have a project where Helvetica Regular, in red appears multiple times across the project’s UI. You have two options: you can either set the appearance from scratch in every case, or you can copy an already decorated instance from previous layouts. In most cases we go with the second option. This is perfectly fine as long as we stick with this design – but in reality design specifications tend to change over time, meaning that copying an already decorated instance won’t help in the long run.
If your client asks for a minor change – to make the text orange, for instance – you’ll end up having to spend hours with a quality assurance specialist doing repetitive and unnecessary work and performing regression tests.
Something as simple as changing the font color in an application can end costing a lot of time. So I thought about potential solutions to this problem, and was inspired by desktop publishing systems like InDesign and Illustrator.
I decided to create a library that would allow developers and designers to easily create and adjust text styles, absolute and relative text size, and other text parameters.
How graphic designers manage text styles
Before decorating any texts, the designer defines a table of styles.
Later, styles are applied to specific text fragments. This means that in future the designer only has to change the style attributes, and all texts that are decorated with this style will update automatically.
Another important feature of text styles is inheritance. All changes made to “parent” style will be reflected in their “children” if the updated attribute wasn’t overridden.
How HandyText library works
A font is the the most basic component of a style. At the same time,
UIFont is not a font in the full meaning of the word. When we say “a font” we actually mean a family of typefaces, but
UIFont defines a single typeface of a specific size.
let font = UIFont(name: "Helvetica", size: 12)
Therefore I have extended my definition of ‘Font’ to mean a structure that includes all available typefaces for a specific font family. For example:
let helvetica = Font( light: "Helvetica-Light", lightItalic: "Helvetica-LightOblique", regular: "Helvetica", italic: "Helvetica-Oblique", bold: "Helvetica-Bold", boldItalic: «Helvetica-BoldOblique")
When file names that are specific to each font are hidden behind common properties, it’s possible to change the text typeface: for example, to make it light, bold or italic.
As I already mentioned, Styles are based on Fonts, which is why a new style instance is created along with a font:
let plaintText = HandyText(font: helvetica)
But a Style also includes various aspects of appearance including size, color, and underline. Style decoration could be set in classic manner by assigning attributes:
style.thickness = .Regular style.size = 15.0
But I prefer a different approach: methods-modificators that return the instance of a modified object. Such methods can be chained:
let plainText = HandyText(font: helvetica).regular().withSize(15.0)
This is more concise than assigning attributes that we mentioned previously. We also needed to define a header style that will be based on
let header = plainText.withSize(18.0).uppercase().bold()
This is what we got in the end:
let bodyString = "This is plain text".withStyle(plainText) let headerString = "This is header".withStyle(header)
The result of applying a style to a string is an instance of
NSAttributedString, which can be displayed in
UITextField and so on.
bodyLabel.attributedText = bodyString headerLabel.attributedText = headerString
Absolute and relative text size
Another important feature of text size is that is that it can be defined with a multiplier:
let header = plainText.withSizeMultiplied(by: 1.2).uppercase().bold()
With a multiplier, if the text size of the base style changes, the whole text scheme will change too.
What can make the text size change? iOS has a Dynamic Type feature that allows you to adjust font size based on device accessibility settings.
let plainText = HandyText(font: .helvetica).withDynamicFontStyle(.Body)
When you use a multiplier, users will be able to change your font scheme more easily – with just a single slider in Device Settings.
Other capabilities of the HandyText library
Our library also provides a number of other tools, including a simple way to define clickable links, the ability to apply styles selectively to highlighted words, and custom tags parser.
Using our library would change the usual workflow for your development team. First, text decoration moves from the Interface Builder to the code itself, which helps us avoid duplication. With a change to just a single line of code you don’t have to worry about updating storyboards, which usually results in multiple bugs.
With the HandyText library, your speed of UI development should increase as well. For example, let’s see how the following UI text layout is defined:
The work in storyboard can be done in seconds, and the whole layout consists of a single text view.
And the decoration in code is readable and expressive:
let lineBreak = NSAttributedString(string: "\n") textView.attributedText = animal.nickName.withStyle(.header2) + lineBreak + animal.species.withStyle(.header1) + lineBreak + animal.about.withStyle(.plainText)
Advantages of HandyText specifications
Normally, text in design specifications looks like a boring list of numbers that are difficult to remembe. But HandyText specifications adopt a more human-readable format.
There could be cases when a specific piece of text has a unique style that differs slightly from the base style. For example, it’s traditional to highlight an error notification by putting it in red.
In this case, there’s no need to define a global text style. A basic style can simply be extended:
And the code tells us the same:
resultLabel.attributedText = «Failure».withStyle(header.withForegroundColor(.red))
HandyText library is a free tool that can save you time at every development stage. It offers designers a more flexible way to describe text parameters, and helps developers save time when creating and modifying layouts and performing regression testing when design specifications change.
Check out all the details of HandyText library at my GitHub account.