For developers: Swift
Chameleon is a collection of reusable tools and components that are divided into multiple packages. They can be used together or separately depending on your team’s needs.
chameleon-swift-tokens
For our design system, Chameleon, we've created an implementation specifically for Swift to make it easy for you to create beautiful and polished user interfaces. Whether you're using SwiftUI or UIKit, our design system provides you with all the colors, fonts, and other atoms you need to bring your UI to life.
Tip: swift demo.
The goal is to enable you to build an app that can efficiently switch its theme context, going beyond changing between light and dark color schemes, and allowing you to theme different parts of your screen. You can subtheme everything, and even load subthemes dynamically, for example, for A/B testing.
More info on how to use it with SwiftUI using ready-made components in chameleon-swift-components. Here, we illustrate how to use the tokens in both multi-themed and single-themed apps. We describe the process of loading dynamic themes in a separate section.
App
- Add Swift Package dependency latest tag https://gitlab.mediahuisgroup.com/frontend/chameleon-swift or branch
- Add both
Chameleon
andChameleonTheme<#theme#>
libraries to your target (optionally addChameleonLegacy
) - Do an
import ChameleonTheme<#theme#>
in code that is specific for your target
Single theme app setup
SwiftUI
import Chameleon
import ChameleonThemeWl
import SwiftUI
@main
struct DemoSingleThemeApp: App {
init() {
ChameleonThemeWl.bootstrap()
}
var body: some Scene {
WindowGroup {
VStack {
ContentView()
}
.styleChameleon(theme, .wl,
// Optional: provide your keyPath here
sdkStyleEnvironmentKeyPath: \.skdStyle)
}
}
}
AppDelegate with or without SceneDelegate
It is the same just make sure you call ChameleonThemeWl.bootstrap()
in the application(_:didFinishLaunchingWithOptions:)
method.
import UIKit
import ChameleonThemeWl
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ChameleonThemeWl.bootstrap()
return true
}
}
Multi theme app setup
A multi theme app setup is available (for non production apps). The app startup is slower due to pre-loading the themes on initialization of the app. The loading of the themes needs to have happened before the app starts.
import Chameleon
import ChameleonAll
import SwiftUI
@main
struct DemoComponentsApp: App {
init() {
ChameleonAll.bootstrapAllThemes()
}
var body: some Scene {
WindowGroup {
ContentView()
.styleChameleonAll(context: Context(theme: .wl))
}
}
}
Roadmap for styles
- improve styling by having default white label styling available in chameleon
- styles will only override the subtheme values that changed for the given context
- token values will be loaded from a new
StyleContextResolver
that will work with the direct output of Figma via Token Studio making it easier to read the embedded json and load token values from string paths.
Writing Previews
Previously it was hard to write previews. This will improve via the styling introduced in V6 and introduction with Xcode 16. For now there are two ways to write previews.
- Just based on
Chameleon
(until chameleon incluedes white label styling this will not be perfect as all colors are black) - Use a custom
Style
struct with better default values - In a non production target/app you can depend on
ChameleonAll
and use theViewModifier
styleChameleonAll
to style your views.
For option 3 the ChameleonAll/styleChameleonAll()
will also lazy load the theme if not already laoded. This way the previews load fast and reliably.
Note: The goal for sdk's is to never bother about theming code. It is the parent view that should provide context or the sdk/app should simply fallback onto sensible defaults.
Consider this simple example showing that will show the headline typography for different themes.
#Preview {
struct Content: View {
@Environment(\.chameleonStyle.semantic.headline2xlTypography) var typography
var body: some View {
Text("Hello, World!")
.typography(typography)
}
}
return VStack {
Content()
.styleChameleonAll(theme: .wl)
Content()
.styleChameleonAll(theme: .ds)
Content()
.styleChameleonAll(theme: .hbvl)
}
}
Note: The
styleChameleonAll
modifier will only be available in non production targets. In production targets you will have usestyleChameleon
or your custom style and load the theme before styling. The reason for this complexity is legal en efficiency. A production app can only contain the theme it uses to have the least possible memory footprint. For legal reasons assets like font files and images can only be included if the app payed the license fee for the assets.
Advanced
Dynamic subTheme loading
Important: import Chameleon using system programmable interface
@_spi(ChameleonTokenOptional) @_spi(ChameleonDynamicLaoding) import Chameleon
so the following works.
To allow for a/b testing, it is possible to create a custom subTheme for the current theme/colorScheme combo in a dynamic way. Adding token values is out of scope for now but the rest is possible via DynamicLoader
.
Any json that maps to the generic type DynamicLoader<V>.Token
can be loaded into the token container. The json provided here is just an example. See the conformance of Codable of any token value type value type in the Chameleon
library for json specifications.
{
"subThemes": {
"yellowPink": {
"colors": {
"buttonBackgroundPrimaryDefaultFill": "#FFC0CB",
"buttonLabelPrimaryDefaultFill": "#E161FF"
},
"borders": {
"buttonPrimaryDefaultBorder": {
"width": 5,
"style": "dashed",
"color": "#FFFF00"
},
"buttonPrimaryDisabledBorder": {
"width": 5,
"style": "dashed",
"color": "#7361FF"
}
}
}
}
}
extension TC {
// assume you have json like above
static let json = "{}"
// Convenience structure to load map to ``DynamicLoader<V>.Token`` types.
struct DynamicSubTheme: Codable {
let subThemes: [String: Values]
struct Values: Codable {
let colors: [String: RGBAColor]
let borders: [String: Border]
}
struct DynamicLoader {
let subThemes: [String: DynamicLoader.Values]
struct Values: Codable {
let colors: [Chameleon.DynamicLoader<RGBAColor>.Token]
let borders: [Chameleon.DynamicLoader<Border>.Token]
}
init(subThemes: [String : DynamicSubTheme.Values]) {
self.subThemes = subThemes
.mapValues({ values in
Values(
colors: values.colors.map { .init(name: $0.key, value: RGBAColor(token: $0.key, lightColor: $0.value)) },
borders: values.borders.map { .init(name: $0.key, value: $0.value)})
})
}
}
}
@MainActor func addSubThemeYellowPink(theme: ChameleonTheme, colorScheme: PlatformColorScheme) {
do {
let input = DynamicSubTheme
.DynamicLoader(
subThemes: try JSONDecoder().decode(DynamicSubTheme.self, from: Self.json.utf8)
.subThemes)
for subTheme in input.subThemes {
try DynamicLoader(\._color, theme: theme, colorScheme: colorScheme)
.registerSubTheme(.custom(subTheme.key), tokens: subTheme.value.colors)
try DynamicLoader(\._border, theme: theme, colorScheme: colorScheme)
.registerSubTheme(.custom(subTheme.key), tokens: subTheme.value.borders)
}
} catch {
print("❌ \(error)")
}
}
}
References
Documentation
Full documentation website & xcode docs for detailed documentation using swift-docc
Extra info
Add Libraries to target
Swift package chameleon-swift
Add dependency via:
In both cases use url: git@gitlab.mediahuisgroup.com:frontend/chameleon-swift