top of page

Why use opt-in design defaults in our code?

Code from Trend • Full Xcode project ↗
import Foundation
import Observation

/// Owns the user's visual preference. This is UI state, so it intentionally
/// lives outside AppModel and never influences application behaviour.
@MainActor
@Observable
final class ThemeManager {
    private static let preferenceKey = "selectedAppColourTheme"
    private let preferences: UserDefaults

    var selectedTheme: AppColourTheme {
        didSet { preferences.set(selectedTheme.rawValue, forKey: Self.preferenceKey) }
    }

    var palette: AppThemePalette { selectedTheme.palette }

    init(preferences: UserDefaults = .standard) {
        self.preferences = preferences
        selectedTheme = preferences.string(forKey: Self.preferenceKey)
            .flatMap(AppColourTheme.init(rawValue:)) ?? .daylight
    }

    func selectNextTheme() {
        let themes = AppColourTheme.allCases
        guard let index = themes.firstIndex(of: selectedTheme) else {
            selectedTheme = .daylight
            return
        }
        selectedTheme = themes[(index + 1) % themes.count]
    }
}

This is Trend’s complete theme manager. It remembers a visual preference and exposes the selected palette. It lives in the presentation layer, outside AppModel, because changing a colour theme should not change how weight entries, goals or progress calculations work.

Shared presentation state is distinct from authoritative business state. This theme manager controls appearance; it does not own the learner’s profile, a weight history or another application capability. Sharing a visual preference does not require adding it to AppModel’s business-feature catalogue.

It is a useful starting point when building a new product. It is not a reason to add themes to an existing app whose designer chose a fixed appearance. The distinction is authority: the toolkit can supply a starting design, but the product team owns the result.

1. The approach

Code from Trend • Full Xcode project ↗
private var rootView: some View {
    RootView()
        .environment(themeManager)
        .preferredColorScheme(themeManager.selectedTheme.colourScheme)
        .animation(.easeInOut(duration: 0.35), value: themeManager.selectedTheme)
}

Trend’s App owns one theme manager and supplies it to the root View through the SwiftUI environment. The selected appearance and animation are applied there, so the screens can share a coherent visual choice.

This is a presentation dependency. CFA does not use the environment to distribute AppModel or feature owners. Those follow the ViewModel connection taught in the other tutorials. Keeping the routes distinct prevents a visual preference from becoming a reason to move business dependencies into the UI hierarchy.

CFA’s documented rationale: new-app defaults help developers establish a usable first version. They do not grant an AI assistant continuing permission to redesign the product. A later decision to remove a theme selector, footer or starter brand must be respected rather than undone on the next task.

2. The problem it solves

Code from Trend • Full Xcode project ↗
struct AppThemePalette {
    // Shared by the streak flame and the action that earns a check-in.
    var streakAccent: Color { .orange }

    let accent: Color
    let chart: Color
    let background: Color
    let surface: Color
    let weightDisplayTop: Color
    let weightDisplayBottom: Color
    let success: Color
    let error: Color
    let warning: Color
}

The palette names roles: background, surface, chart, success, error and warning. A new screen can use those roles without choosing unrelated colours for the same meanings. That is helpful when deliberately constructing a visual system.

But adopting CFA in an established app is an engineering change, not agreement to replace its visual system with this palette. Automatically adding theme menus, architecture branding or new icons would change the product outside the requested migration. Repeatedly suggesting them can be disruptive even if no code changes.

Trend’s fixed orange streak accent is also an intentional visual choice in this source. A semantic palette does not mean every pixel must change with every theme. It means the team can identify which roles vary and which artwork or accents remain fixed.

3. Why it works

Code from Trend • Full Xcode project ↗
@Test func remembersTheSelectedTheme() {
    let preferences = isolatedPreferences()
    let manager = ThemeManager(preferences: preferences)

    manager.selectedTheme = .midnight

    #expect(ThemeManager(preferences: preferences).selectedTheme == .midnight)
}

This test changes the theme, then constructs another manager with the same isolated preferences. The second manager reads the chosen identifier. That is the behaviour the persistence key and enum raw value are preserving—not just an attractive colour in the current preview.

Changing that key during a tidy-up could reset someone’s choice. Replacing the available theme identifiers could do the same. Design changes need compatibility decisions just as other preferences do.

Apple’s environment mechanism makes a shared presentation value available to descendants. It does not guarantee readable contrast, a good transition or correct behaviour in every sheet. Those require rendered checks, including large text and both system appearances.

4. Advantages of using it

Code from Trend • Full Xcode project ↗
@Test func startsWithDaylightWhenNoPreferenceExists() {
    let preferences = isolatedPreferences()

    let manager = ThemeManager(preferences: preferences)

    #expect(manager.selectedTheme == .daylight)
}

This test records Trend’s actual default: Daylight when no preference exists. The current new-app toolkit defaults are different—they offer an Automatic mode and a broader palette selection. That does not make this existing app broken or authorize changing its default during maintenance.

Editorial trade-off: defaults save decisions at the start of a project but can become unwanted policy if applied repeatedly. Record whether the work is new-product development or established-product maintenance. In either mode, a focused request such as fixing one button colour remains focused.

For a new app, verify its icon, launch appearance, colour roles and Settings information as part of the chosen design. For an existing app, preserve its branding and interfaces unless the owner asks for a change. The same engineering boundaries can support both: a useful starting point without an assistant that keeps redesigning someone else’s product.

Try this with the CFA AI Toolkit for iOS:

Using CFA for iOS, create a new app using the agreed product design and appropriate starter defaults.

Scope: code from the linked app revisions; smaller excerpts belong to their containing types and targets. The articles distinguish older implementations from current CFA conventions. Source verification is not a full app build or device test. Suggested checks are guidance, not claims they ran against your application.

bottom of page