Greetings, traveler!
With SwiftUI, it’s super easy to change your app’s theme. I’m gonna show you how to change the color scheme and save the new value in UserDefaults using the @AppStorage property wrapper. By the way, in the last article, we talked about @Appstorage and how you can use it.
AppearanceKind
Let’s create an enumeration to contain options for the user interface styles. To select from the menu, we must mark our enumeration as CaseIterable and Identifiable and create some properties to store the values of the corresponding images, colors and names. We also need to specify that the rawValue of enumeration cases has an integer value so that we can save them in UserDefaults and use for setting desired app theme.
enum AppearanceKind: Int, CaseIterable {
case system = 0
case light = 1
case dark = 2
}
extension AppearanceKind: Identifiable {
var id: Self { self }
}
extension AppearanceKind {
var title: String {
switch self {
case .system: "System"
case .light: "Light Mode"
case .dark: "Dark Mode"
}
}
var imageName: String {
switch self {
case .system: "sun.haze.fill"
case .light: "sun.max.fill"
case .dark: "moon.stars.fill"
}
}
var color: Color {
switch self {
case .system: .green
case .light: .orange
case .dark: .purple
}
}
}
ThemeManager
So, we gotta find the guy who’s gonna change the color scheme, right? That’s the ThemeManager. It should have a function that lets you change the userInterfaceStyle property value of the active window. We will use the enumeration we created earlier to modify the value of the property. The modified value will be stored in UserDefaults, so we will use the @AppStorage property wrapper to retrieve the value.
import SwiftUI
final class ThemeManager {
@AppStorage("selectedAppearance") var selectedAppearance: AppearanceKind = .system
func overrideDisplayMode() {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.overrideUserInterfaceStyle = .init(rawValue: selectedAppearance.rawValue) ?? .unspecified
}
}
View
Let’s create a View now and use the entities we created earlier. We can use the values inside the menu since our enumeration was marked as a CaseIterable. Each menu item will be a button that overrides the userInterfaceStyle value with the help of our ThemeManager. We’ll also define it when our View appears.
struct ContentView: View {
@AppStorage("selectedAppearance") private var selectedAppearance: AppearanceKind = .system
private var themeManager = ThemeManager()
var body: some View {
Menu {
ForEach(AppearanceKind.allCases) { appearance in
Button {
selectedAppearance = appearance
} label: {
HStack {
Text(appearance.title)
Image(systemName: appearance.imageName)
.renderingMode(.template)
.foregroundColor(appearance.color)
}
}
}
} label: {
Image(systemName: selectedAppearance.imageName)
.renderingMode(.template)
.font(.system(size: 25))
.foregroundColor(selectedAppearance.color)
}
.onChange(of: selectedAppearance) { _, _ in
themeManager.overrideDisplayMode()
}
.onAppear {
themeManager.overrideDisplayMode()
}
}
}
Conclusion
So that’s it! We can now easily manage our app’s theme. Hope you found this tutorial useful.
See you soon!
Check out other posts:
- How to change the order of Calendar weekdaySymbols
- Mutex in Swift 6 and Synchronization
- Exploring the take() method for Optionals
- How to display an icon with a title using the Label in SwiftUI
- How to dysplay byte counts in Swift
- How to format date in SwiftUI
- How to use Hierarchical Colors in SwiftUI
- Global actors and Singletons
- How to organize layout with ControlGroup in SwiftUI