Design System: How to define a visual identity to my app
When you are working on a huge application in the long term, you shall think about all the paths it may take along the time. How is the interface evolving? which color palette should I use? How should the layout be defined? How should I organize the relationships between each widget? Answering all this questions, there is an essential element of your project that you must define since its conception: the Design System.
The Design System is a very important component of your app that is usually placed entirely inside a separated module. Talking once more about architectures, when we build a screen(UIViewController), it's designed around a set of business rules within that context, or maybe it can be reused in another one to recycle the layout. But we must consider that there are a lot of components, descendants of UIView, that are placed in multiple screens across different contexts. They don't have any business rule inside, nor any logic, they just build some reusable UI, like some custom buttons, a toolbar or even some containers to store some data. Also, since our app shall have a color palette, a typography and some images, we can englobe the visual identity inside this module. In this article we are going to talk about all the core aspects to build a good Design System to your app(or maybe to give support to an already existing one)
Benefits of a Design System
- Improves modularization
- Keeps business logic and UI even more separated
- Creates a way of testing each component or resource without needing to inject that in an app feature
- Testing is fast
- Easy to find each resource you need
- UI can be shared across multiple apps(good for huge companies with several apps)
- Easy to migrate to a new visual identity without harming any team's work
Componentization
When we talk about componentization(which is a requirement for most iOS job positions anyway), a lot of words may come into your head. What you really must understand is that what you are really doing is building a UI widget class to be reused across the whole application. Let me give you an example:
Suppose you are in charge of building a new screen for your company's app, and for that you must place into that screen some visual components that are already present in other screens. The question is: How do you know if those components already exist in other contexts and how can I reuse them in order to avoid duplicated code?
Well, there are a few tools that can be used in order to check that, one of them, which is my favorite, is called Abstract. This is a tool made for designers to have a version control upon their work, being able to retrieve old components or screens that were created some time before that have changed across time. There you can find all UI widgets, assets and other kinds of resources you need and even have an identifier for that resource in case you need the Swift/Objective-C class name in an organized fashion.
The next step is to create the class for that UI component in Swift and test if that fits our needs. Some concepts around the component must be considered before coding. Let's suppose we are about to build the following component:
This component is known as a Toast. It just consists of an alert that pops up on your screen and stays in there for a few seconds. After that time, it fades out. This component has 3 different states according to the context you want to present it: success, error and warning.
Component states
Each UI component may have multiple states reflecting the context you want to present it. Instead of injecting a whole lot of parameters when instantiating the your class, you may pass an enum as a state to that reflecting what you want and the case of that enum will set the custom UI parameters by itself.
Back to our example, our ToastView must have three states:
Those are the only appearances our component must possibly present and the idea is that our injected state reflects each UI property, in our case, only the background color and text. How can we achieve that result in a simple way?
Create an enum:
This is a public enum where each case reflects some UI properties we want for our component depending on its state. Each state has a corresponding color and a text attached to it. So, when we are about to set it up, all the properties are configured in a few lines of code without relying on some manually passed data:
This article is just supposed to list the proposals of a Design System on a huge app, so we are not covering other good practices within componentization here today.
Assets
Assets consist of every external resource our app may need in order to render its screens. It may include images, typography, colors, animations and everything needed to set up our UI. What you must keep in mind is that all of them are able to be used across the whole application, no matter the context. Design System is not attached to any feature our use case, in this order, it is just a tool to show all the interfaces we need for any context. Said that, the right way to deal with that is by keeping all data grouped in a single place according to its type:
Here we have an enum corresponding to our color palette. The colors that we shall use in the entire application can be found here. As we said before, when we are checking some screen specification, we may identify the color, images and font names in the UX tool in order to find it in our design system. If it's not there, we can(and must) create them by ourselves. Each color in this enum corresponds to a raw color from the UIKit according to its RGB code.
The idea basically is: if for instance you find in the Abstract(or any other tool) a screen with a color defined as attentionYellow
, you must find it in the enum and assign it to the described component.
The same pattern can be applied to images and fonts, build an enum for each of them and list all resources you may need in your interface:
At this fashion, you have no efforts at all to create some new UI parameters and avoid replicated code.
Spaces and dimensions
I don't understand too much about UX, but I know that any good interface must follow some pre-established rules. For keeping a suitable look-and-feel, all margins and size measurements must be multiple of four. This way we can define into some words how big or small we want it to be without even knowing the exact frames:
Repair that in our design system, in order to keep a proportional layout, we are defining our margins to be always multiple of four or even only powers of two. Please, always avoid odd numbers.
Testing my Design System
Now that you are totally able to create your own Design System for your dream project, it's very important to you to be able to test every component class, every action, every resource in a separated application in order to make it fast and not compromise your app itself. When you create a new module, no matter UI or not, you must create a Sample application together with it in order to test it without taking too long to build your app. If in a company, it's important by means of documentation that all the iOS skill can check all the components defined in the design system. The best pattern for that? I will show you.
We are going to define within our Sample a new screen(it can be the default one in the standard Storyboard) that has a table view corresponding to each component you want to check, first create this enum in a separated file:
Create this enumAllComponentsLibrary
and define each case for a component you want to be listed.
Now there are two things you need to define in order to show each component in a separated screen when clicking its cell: a text to be displayed and a custom ViewController that holds your component that shall be rendered. You can defined those parameters according to your context at this class:
Now, create a new ViewController ComponentsLibraryController
and make it conform with the TableView protocols. It must also keep an array of all the components that shall be presented:
We will not even build a custom UITableViewCell since we are about to use the most standard one. We are about to assign the component text to each cell according to the array index:
See that we assign the text to the cell textLabel
according to the component text at the indexPath. Also, we instantiate the ViewController to be presented according to the ViewController class returned by the enum case.
In each ViewController that shall be presented after clicking a cell, we must present the corresponding component with all its states in order for your iOS skill to know that it actually works as we want and will not face any bugs.
A tip I shall give you is to place an image of your component or resource in its corresponding Pull Request when created, but since it's already inside your Sample, we can say that it's already unit tested.
What to be careful with when dealing with a DS
- Not to create duplicated components that do the same thing
- Not to create a new component very similar to an already existing one when the new one can be seen as a state to the first
- Be careful when dealing with an already existing component because any bug with it may spread in the entire app
- Test everything you create in the Sample
- Avoid creating components outside the Design System and using crude measurements since it must escape the DS patterns
Conclusion
In this article we defined one of the most powerful concepts that we need for a huge app in order to maintain its UI scalable, testable and sustainable in long term without compromising any feature. All the resources inside our DS can be reused by the entire iOS skill and it's very important to have contributions from anyone that uses this framework. I hope you are now very excited to build your own app's skeleton and are even thinking about how to expand that across multiple targets. I hope you have enjoyed it!