Swift Enums 🤝 Design Systems
Utilise the Swift type system for simple, customisable SwiftUI components
Subscribe to Jacob’s Tech Tavern for free to get ludicrously in-depth articles on iOS, Swift, tech, & indie projects in your inbox every two weeks.
Paid subscribers unlock Quick Hacks, my advanced tips series, and enjoy exclusive early access to my long-form articles.
If you’ve worked on any large development projects, you’ll likely have encountered a design system. A design system is a library that encapsulates your branding into reusable components, colors, fonts, icons, and motifs, to help ensure a consistent style across your products.
In short, they allow us to avoid re-inventing the wheel for every screen of UI design.
Personally, I love working on things to make life easier for my team. Implementing design systems in a clean, straightforward way has huge leverage since your team will work with it every day.
In my career, I’ve implemented various design systems, and explored many diverse approaches, such as:
UIKit Storyboards and Nibs
Programatically drawn components for each UIKit subclass
Protocols to allow composable drawing and animation
Enums to declaratively define components
Every approach has its place, but in SwiftUI I’ve found the cleanest, most useable approach by far is defining your components using enums.
As you read through this post, I’ve added many code snippets, but feel free to checkout my code from Github and follow along.
The design system
Let’s say your designer has just sent you a link to their designs - it’s the button component they’ve been working on. It might look something like this:
Generally speaking, any designer worthy of their Figma free tier or Sketch license will have a small set of styles, in a small palette of brand colors, and multiple sizes, icons, and alignments.
You might feel the gears turning in your head as you start to realise why Enums might be a strong approach here.
Define your button
Let’s start by defining our Button. Since we’re in SwiftUI-land, we can start by defining a View
struct.
Here, for simplicity’s sake, I’ve called it MyButton, but generally you will usually prefix your company name - in my time I’ve built DeloitteButton
, CarbnButton
, and Gener8Button
. Personal project? Feel free to name it [YourFirstName]Button
!
To start with, we simply initialise the button the same way we initialise a default SwiftUI button - with a title and an on-tap action.
Your first enums
Now that we have the basics in place, we can start to make it interesting.
We can start with the most simple features of the buttons in the design system, and work down through the levels of complexity and customisation. So to start, let’s deal with the colors.
In the design system above, we’ve got 3 main colors:
Default buttons - using our main brand color, blue.
Accent buttons, which use our secondary brand color, a pale green.
Error buttons, which are a light red
We can create these options in a MyButtonColor
enum to handle this cleanly:
Here, we’re harnessing the astonishing power of Swift’s enumerations - when we add computed properties like mainColor
and detailColor
to the enum, your case switches over self
and finds the value defined for itself!
Quick explainer: Since default
is a Swift keyword, we need to ‘escape’ it with back-ticks if we want to call one of our enum cases default
, or the compiler will get confused.
Now we can modify our MyButton
’s body
property to include these color properties when drawing:
Now, finally, we can modify the initialiser of MyButton
to expose this type as an argument. Since our .default
button color is the most commonly used, we should use it as the default argument for color
in this initialiser:
Now, your team can use MyButton
the exact same way it uses a default SwiftUI Button
and draw our .default
(blue) button. Our team can also use the more complex version of the initialiser when they want to override the default colour for the .accent
(green) or .error
(red) variants.
Interlude: Progressive Disclosure
My approach is inspired by Apple’s WWDC22 video, The craft of SwiftUI API design: Progressive disclosure.
This talk draws a comparison between progressive disclosure in UI design with progressive disclosure in API design. Progressive disclosure is the concept of making basic options easy to access, but enabling more complex use cases to be revealed as you need them.
Apple are the masters of this; from their operating system (Dock → Finder → zsh shell) to the brand-new Swift concurrency features (async/await → Task → actors & task groups).
The key to API design is trying to make the most common use cases easily accessible, with intuitive defaults. You can optimise the call site of your API so it’s easy to either roll with the defaults or pick the best approach for your use case.
Adding images to our button
According to Apple, a crucial aspect of progressively-disclosed API design is composability over enumerability. This doesn’t literally mean “don’t use enums” - it means we should allow consumers of our API the freedom to customise where possible.
In this instance, we’re going to create a new enum, MyButtonIcon
, which takes advantage of enum Associated Values to allow any Image we like to be added to the button!
By default, we don’t want any icon on our button - therefore, we should make this MyButtonIcon
property optional with the default value as nil.
Now we can set this up in our MyButton
’s body
:
Here, we create a new buttonContent
subview in the buttonForColor
, which contains a SwiftUI HStack
. Based on the enum case for our icon
, we unwrap the optional on either the leading or trailing side of the original button Text
.
Using optional pattern matching, we can take the associated value for the Image out of the enum and place it in our view.
We pass this into a new iconView(for image: Image)
method to add standard SwiftUI image customisation to it, including setting renderingMode
to .template
, which means it’s color will be set to our MyButtonColor
’s detailColor
.
The rest of the owl
After implementing more enums to handle size and style, we’re left with an initialiser that looks like this:
We’re embodying the principles of progressive disclosure here:
The call site hides non-essential details, and can be used exactly like a default SwiftUI
Button
, with a title string and an action, but developers can customise it easily when needed.When customising the button, Xcode autocomplete for our enum cases means that even somebody new to your project can intuit which properties they should set to match the design.
Intelligent default values in the initialiser arguments mean that the most common use case - a large, blue, filled button, with no icon - is the easiest to create.
Consumers of our API can customise the button with any icon they choose by adding an associated value to the icon enum.
Let’s see this in action.
When we actually use the button in our Views, we can invoke it with no customisation:
MyButton(title: "Press me!", action: { didPressButton() })
Or we can utilise all the the features we’ve set up for a more customised button:
MyButton(type: .secondary,
color: .accent,
size: .small,
icon: .trailing(.Icon.profile),
title: "Press me!",
action: { goToProfile() })
Full sample code
Conclusion
Through building MyButton
, not only have we created a useful UI component - we’ve hit on many of the powerful features of the Swift type system:
Enums to help us define specific customisation options
Computed properties inside enum definitions which switch over the cases
Associated values in enum cases to allow for full customisation
Default parameters in the
MyButton
initialiser offers progressive disclosureOptional properties allow us to avoid using functionality we don’t need
I hope after reading this, you will think about utilising Enums in your code to bring design systems to life in SwiftUI.
In SwiftUI the preferred way to style buttons is not — as shown here — by creating a view with a button in its body, but by providing a button style. you can check my repository to see how this is done, not only for buttons but toggles and textfields, too: https://gitlab.com/vikingosegundo/button-and-toggle-styles