ReviewOS

craft-native/craft

No way to choose Light or Dark: the window's interface style is never overridden

#389
Closed glennmichael123 opened this 8 days ago ·
8 days ago

An application cannot offer Light / Dark / System. Native screens follow the system and there is no way to say otherwise, at launch or at runtime.

Asking from HQ.training, where Settings should carry the appearance control almost every iOS app has.

What is there

CraftNativeScreen.swift:5944 decides a screen's scheme:

private var colorScheme: String {
    let traits = [view.window?.traitCollection, CraftPresenter.keyWindow()?.traitCollection, traitCollection]
    if let style = traits.compactMap({ $0 }).first(where: { $0.userInterfaceStyle != .unspecified })?.userInterfaceStyle {
        return style == .dark ? "dark" : "light"
    }
    return config.appearance == "dark" ? "dark" : "light"
}

So there is a config key — craft.config.json carries "appearance": "system" — but it is only reached when every trait collection in that list is .unspecified. On a device one of them always is specified, so the config value is effectively unreachable and the system always wins.

Changes are observed, not controlled: traitCollectionDidChange refreshes theme defaults (:1419) and re-renders (:5399), and sendAppearance publishes an APPEARANCE payload to the script. All of that reports the system's decision.

What is missing

  • overrideUserInterfaceStyle is never set anywhere in the iOS templates. That is the one UIKit lever that makes an app's own choice stick, and nothing touches it.
  • No action is registered for it. The dispatcher's closest neighbour is ("StatusBar", "setStyle"), which styles the status bar rather than the interface.
  • Nothing named appearance exists in the published TypeScript surface (craft-native/dist/types.d.ts: 0 matches), so a screen has no way to ask.

An application can therefore style its own views with dark: classes, and those classes resolve against a scheme it cannot influence.

Suggested

Two pieces, and the first is the useful one on its own:

  1. A runtime setter — craft.app.setAppearance('system' | 'light' | 'dark'), applying overrideUserInterfaceStyle to the key window so every screen, sheet and alert follows. system maps to .unspecified, which restores the current behaviour exactly.
  2. Honour config.appearance at launch, by applying the same override when it is not "system". Today that key reads as though it works and does nothing, which is worse than not having it.

Persisting the choice belongs to the application — it is a stored preference like any other, and craft.preferences already exists for it. What is missing is only the ability to act on it.

Why it is worth having

Appearance is not decoration for a training application. Phones live in gyms at 6am and beside beds at night, and plenty of people run one app dark and everything else light. It is also among the first settings anyone looks for, so its absence reads as an omission rather than a decision.

Related: this is the same shape as craft-native/craft#387 — the machinery is present and observed, and only the lever to act on it is missing.

Sign in to comment on this issue.