E2506
Dev_Log

Implementing themes and custom color schemes in SwiftUI.

SwiftUI’s @AppStorage is useful for storing simple user preferences, but it only supports basic data types such as String, Int, and Bool , making it less suitable for complex data structures.

We can declare a custom enum to represent different themes, such as light, dark, and system. And conform it to the String or Int protocol that supported by @AppStorage. This allows us to store the user’s theme preference in UserDefaults and apply it throughout the app.

Here is the code.

1enum Theme:String, CaseIterable {
2    case light
3    case dark
4    case system
5    var colorScheme: ColorScheme? {
6        switch self {
7        case .light:
8            return .light
9        case .dark:
10            return .dark
11        case .system:
12            return nil
13        }
14    }
15}

And example for tint color.

1enum TintColor: String,CaseIterable {
2    case blue, green, red, yellow, purple, pink, orange, brown, gray
3
4    var color:Color {
5        switch self {
6        case .blue:
7            return Color.blue
8        case .green:
9            return Color.green
10        case .red:
11            return Color.red
12        case .yellow:
13            return Color.yellow
14        case .purple:
15            return Color.purple
16        case .pink:
17            return Color.pink
18        case .orange:
19            return Color.orange
20        case .brown:
21            return Color.brown
22        case .gray:
23            return Color.gray
24        }
25    }
26}

Since the enum conforms to CaseIterable, we can easily generate a list of available themes and colors for users to choose from in the settings view. You can then bind the selected theme and tint color to the settings view using @AppStorage, like this:

1struct SettingView: View {
2    @AppStorage("theme") private var theme: Theme = .system
3    @AppStorage("tintColor") private var tintColor: TintColor = .blue
4    var body: some View {
5        Form{
6            Picker("Theme", selection: $theme) {
7                ForEach(Theme.allCases, id: .self) { theme in
8                    Text(theme.rawValue.capitalized).tag(theme)
9                }
10            }
11            Picker("Tint Color", selection: $tintColor) {
12                ForEach(TintColor.allCases, id: .self) { color in
13                    Text(color.rawValue.capitalized).tag(color)
14                }
15            }
16        }
17    }
18}

Now you can apply the theme and tint color to your main view using preferredColorScheme and tint modifiers.

1@main
2struct NavigationDemoApp: App {
3    @AppStorage("theme") private var theme: Theme = .system
4    @AppStorage("tintColor") private var tintColor: TintColor = .blue
5    var body: some Scene {
6        WindowGroup {
7            ContentView()
8                .preferredColorScheme(theme.colorScheme)
9                .tint(tintColor.color)
10        }
11    }
12}

The code is available in this repository

Just noticed the color change doesn’t apply immediately on the picker. I've submitted feedback to Apple about this issue.

Last Update: Jul.2025