Edit #2
Here are 2 completes solutions:
- Giving a control of mode status (Light or Dark) at Application Level
- Giving a control of mode status at View Level
Both are updated on the fly when Mode status changes.
Both work in Preview and on true device.
Both manage a Color Theme based on Mode status (inspired from JetPack Compose).
In solution 1, OSModeTheme & OSModeThemeUpdater work together to provide the right mode status and color theme as static values, offering the possibility to define colors at the top level, eg: in default values of a view init parameters.
init(color: Color = OSModeTheme.colors.primary) { ... }
In solution 2, OSModeThemeProvider is a View Wrapper providing a local variable containing the right ColorTheme according to the Mode status.
OSModeThemeProvider { colors in
Text("Foo bar")
.foregroundColor(colors.primary)
}
// Commun part
protocol Palette {
static var primary: Color { get }
static var primaryVariant: Color { get }
static var secondary: Color { get }
static var secondaryVariant: Color { get }
static var accentColor: Color { get }
static var background: Color { get }
static var frame: Color { get }
static var error: Color { get }
}
struct LightColorPalette: Palette {
static var primary = ColorPalette.black
static var primaryVariant = ColorPalette.grayDark
static var secondary = ColorPalette.grayMid
static var secondaryVariant = ColorPalette.grayLight
static var accentColor = ColorPalette.blue
static var background = ColorPalette.white
static var frame = ColorPalette.grayDark
static var error = ColorPalette.orange
}
struct DarkColorPalette: Palette {
static var primary = ColorPalette.white
static var primaryVariant = ColorPalette.grayLight
static var secondary = ColorPalette.grayLight
static var secondaryVariant = ColorPalette.grayMid
static var accentColor = ColorPalette.blue
static var background = ColorPalette.black
static var frame = ColorPalette.grayLight
static var error = ColorPalette.orange
}
// Solution 1
class OSModeTheme {
static var colorScheme: ColorScheme = .light
static var colors: Palette.Type = LightColorPalette.self
static func update(mode: ColorScheme) {
colorScheme = mode
colors = colorScheme == .dark ? DarkColorPalette.self : LightColorPalette.self
}
}
struct OSModeThemeUpdater<Content>: View where Content: View {
@Environment(\.colorScheme) var colorScheme
let content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
var body: some View {
OSModeTheme.update(mode: colorScheme)
return content()
}
}
struct OSModeThemeDemo: View {
var body: some View {
OSModeThemeUpdater {
ZStack {
Rectangle()
.fill(OSModeTheme.colors.background)
VStack {
Group {
Text("primary")
.foregroundColor(OSModeTheme.colors.primary)
Text("primaryVariant")
.foregroundColor(OSModeTheme.colors.primaryVariant)
Text("Secondary")
.foregroundColor(OSModeTheme.colors.secondary)
Text("secondaryVariant")
.foregroundColor(OSModeTheme.colors.secondaryVariant)
Text("accentColor")
.foregroundColor(OSModeTheme.colors.accentColor)
Text("background")
.foregroundColor(OSModeTheme.colors.background)
Text("frame")
.foregroundColor(OSModeTheme.colors.frame)
Text("error")
.foregroundColor(OSModeTheme.colors.error)
}
}
}
}
}
}
// Solution 2
struct OSModeThemeProvider<Content>: View where Content: View {
@Environment(\.colorScheme) var colorScheme
let content: (Palette.Type) -> Content
init(@ViewBuilder content: @escaping (Palette.Type) -> Content) {
self.content = content
}
var body: some View {
content(colorScheme == .dark ? DarkColorPalette.self : LightColorPalette.self)
}
}
struct OSModeThemeProviderDemo: View {
var body: some View {
OSModeThemeProvider { palette in
ZStack {
Rectangle()
.fill(palette.background)
VStack {
Text("primary")
.foregroundColor(palette.primary)
Text("primaryVariant")
.foregroundColor(palette.primaryVariant)
Text("Secondary")
.foregroundColor(palette.secondary)
Text("secondaryVariant")
.foregroundColor(palette.secondaryVariant)
Text("accentColor")
.foregroundColor(palette.accentColor)
Text("background")
.foregroundColor(palette.background)
Text("frame")
.foregroundColor(palette.frame)
Text("error")
.foregroundColor(palette.error)
}
}
}
}
}