In our app, we have a basic colour palette used for the main design, however there is also the possibility for the user to choose their own colours via an admin system (for white labelling purposes). These colours are returned to us as hex strings and stored in a BusinessProfile
object using the following model:
struct BusinessProfileColors: Codable {
let success: BusinessProfileColor?
let warning: BusinessProfileColor?
let highlight: BusinessProfileColor?
// ... more colours here
}
struct BusinessProfileColor: Codable {
let light: String?
let dark: String?
}
So I am trying to create a class which will check for the presence of valid hex colours in the BusinessProfile
and use these (with the correct light/dark mode colours selected according to the app state). If the colour value for a specific palette is nil
or not a valid hex string, we will fall back on the standard design colours. Here is how the class looks:
class ColorPalette: ObservableObject {
@Environment(\.colorScheme) var colorScheme
let container: DIContainer
init(container: DIContainer) {
self.container = container
}
private func dynamicColor(lightColor: Color?, darkColor: Color?, defaultColor: Color) -> Color {
switch colorScheme {
case .dark:
return darkColor ?? lightColor ?? defaultColor
default:
return lightColor ?? defaultColor
}
}
var alertSuccess: Color {
return dynamicColor(
lightColor: Color(hex: container.appState.value.businessData.businessProfile?.colors?.success?.light),
darkColor: Color(hex: container.appState.value.businessData.businessProfile?.colors?.success?.dark),
defaultColor: Color("Success"))
}
// More colours follow the same pattern
}
The idea is that I want to pass this to my views as an @EnvironmentObject
like this:
@EnvironmentObject var colorPalette: DynamicColors
So that within these views I can declare the colours I want simply like e.g.:
text
.foregroundColor(colorPalette.alertSuccess)
So I am declaring an instance of this class in my first parent view (RootView.swift
) as an @StateObject var
as follows:
@StateObject var colorPalette: DynamicColors
So when I first initialise RootView it is like this:
RootView(viewModel: RootViewModel(container: viewModel.environment.container), colorPalette: .init(container: viewModel.environment.container))
(NB: the viewModel.environemnt.container
is where we store the BusinessProfile
).
Then I am passing this as an environment object from the RootView to the first view where I need the colours like this:
TabBarView(viewModel: .init(container: viewModel.container))
.environmentObject(colorPalette)
However, in TabBarView
, whilst the colours from the API results are returning and being displayed, the light/dark mode switch does nothing. Only the light mode colours are showing.
If I change the setup so that I declare the ColorPalette
individually on each view and pass in the colorScheme
from here all works fine, e.g.:
struct SomeView: View {
@Environment(\.colorScheme) var colorScheme
let container: DIContainer
var colorPalette: ColorPalette {
ColorPalette(colorScheme: colorScheme, container: container) // Pass in the color scheme from the current view and use this in the ColorPalette class
}
}
However, this seems extremely heavy handed and I would much rather use @EnvironmentObject
as it seems well suited to this scenario. However, how can I leverage the @Environment(\.colorScheme)
property in this scenario from within my ColorPalette
class?