Attributed String with Style
Have you ever had the feeling that your
NSAttributedString were not in the right place in your code? That you were mixing view details and data logic?
It happened to me recently and I want to propose a solution that focus separately on the semantics and on the visual representation of an attributed string.
Apple defines an
A string that has associated attributes (such as visual style, hyperlinks, or accessibility data) for portions of its text.
In general, I use
NSAttributedString to highlight parts of a label content (with bold font, underline, etc.).
For instance, for one of the last app I worked on, I had to be able to search through a list of users. As I type in the search bar, users are filtered based on the query, and their names contain a visual indication of the query match. This visual clue is implemented with an
Let’s see the relevant parts of the implementation to understand what is wrong.
The view is a subclass of
The cell is configured with a ViewModel, a dumb struct that holds the data to display.
Note that the
typeLabel visual attributes are defined in the view, but not the
nameLabel ones, that live in the attributed string.
The glue code between the view and the view model is a Mapper: an object that takes an entity (in our case a
User), and returns a view model to display. This is where the attributed string is created.
This works pretty well, but a detail was bothering me. I do not want to set up the visual style of my strings in a mapper. The mapper’s only job is to convert model data to view data. It should not be aware of the way the data is displayed to the end user.
But at the same time, the mapper needs to know how to format the data, and in our case, which part of the username is highlighted. And I don’t want my view to know about the mapping operation. The view is dumb and should only display whatever data we pass in.
The solution is to split the concerns. We need to create semantic styles. These styles will be used on the attributed string in the mapper. Then the view will associate a style to visual attributes (color, font, etc.). This way the mapper only knows about the semantics, and the view about the display. It’s the same idea behind HTML and CSS, the html should be a bunch of semantic data and CSS a list of rules to style the data.
The mapper now becomes:
Note that I don’t use
.font attribute anymore but a new custom
.semanticStyle property that is completely independent of
The styles, that can be anything, are in my case defined in the view model.
Now in the view, I can choose which
UIKit attributes are associated to each style.
If you wonder, the
styler is rather simple. It just enumerates all the attributes and replace the
semanticStyle occurrences with registered attributes for the current style. You can find the implementation on my Github repository.
Now the code feels right and the concerns are respected. For sure, it adds a layer of complexity, but it clearly separates the view and the mapping logic. On one side we focus on the semantics, on the other side on the display. That means the mapper does not have to evolve if the design change in the future. Conversely, the view will not change its attributes if the backing data is updated (for instance, if we modify the highlighting feature).