Adding themes to a SwiftUI app using Environment Values
I recently started working on adding the concept of “themes” to an app I’ve been working on. I plan on theme-ing being a part of my monetization plan - But I also hate looking at ugly apps and right now mine is very boring and ugly and a reminder that I am not in fact a designer.
Step 1: Pick out base colors
First thing I needed to do was pick out a color palette that didn’t make my eyes bleed. Since I am not an expert in color theory I turned to the experts by googling “color palette” and came across Color Hunt. They have a ton of different palettes and had the Hex and RGB values easily available which was a huge plus. I looked for a palette that I aesthetically enjoyed and that had colors that I could reasonably see being used both because they were pretty but also could be used to convey meanings for actions such as a color for a “cancel” or “submit” button. After picking out a simple palette that had colors I could use I also bookmarked the website for future use.
Step 2: Create Color Resources
Next step I want to add the colors to an asset catalog in my project. If you already have a project started you probably already have an Assets.xcassets but if you don't or you want a new one you can add it by creating a new file either by going to File->New->File From Template or by hitting CMD+N. From the pop up just scroll down to "Resource" and you will see an option called "Asset Catalog". I created a new one and named it "Colors".
From here I can add a new Color Set and define the colors that I want to have available in the app. I give the color asset a name and if I select the color block and open up the right side panel I can change the Color input method to 8 bit hexadecimal and set the color I picked out. You can also set dark mode versions of the colors here as well so when the interface changes the colors will automatically update.
Step 3: Create Color Extentions
This step is completely optional - I just find it more convenient. Instead of having to reference the new color asset anywhere I want to use it as follows:
view.background = Color("colorLightYellow")
I prefer adding an extension to Color and defining a named value so that I can reference it simply from anything expecting a Color.
extension Color { static var lightYellow = Color("colorLightYellow") }
Step 4: Add a theme
There’s probably a million different ways to do this and I can almost guarantee that I’ll be completely changing this up in the future, but for now this is simple enough and works well alongside Environment Values to make it simple to set a theme near the top level of the app and let it propagate down to the base views.
Create an enum named Theme and define the color values you want for different meanings.
enum Theme { case standard var primaryColor: Color { switch self { case .standard: return .lightYellow } } }
You can expand this to cover a wide variety of colors and their purposes. For example I have set up a primary color, secondary color, negative color, affirmative color, and tint color for each theme.
Step 5: Add an environment value
You will need to create an extension for EnvironmentValues and add your value:
Code block defining a custom Environment Value
Once this exists you can set the theme at the main app level and it will be propagated down to the rest of the views.
@main struct MyApp: App { var body: some Scene { WindowGroup { LandingView() .environment(\.theme, .standard) } } }
That’s it!
Fairly simple! Once the value is propagated down you can read the environment value in other views and use the theme colors how you see fit. I added the ability to pass the theme colors to some view modifiers so that things could be styled according to the themes themselves and still stay fairly self-contained. In the future depending on how intense theme-ing gets I can imagine a need to move away from an enum definition and to something more object-encapsulated. But for now this works great!