SwiftUI:Sonoma及以上版本ColorScheme切换时已打开文档窗口不更新
解决SwiftUI已打开文档窗口外观不更新问题
问题场景
开发的SwiftUI应用包含文档窗口和设置窗口,更改外观选项后:
- 新打开的窗口能正确切换亮/暗/系统外观
- 已打开的设置窗口外观正常更新
- 已打开的文档窗口外观无变化
使用环境:Sonoma系统、Xcode16.1(旧版Xcode也存在该问题)
原代码问题分析
原代码中,@AppStorage("appearanceSelection")存储数字类型,appearanceSwitch作为计算属性返回ColorScheme?,但WindowGroup的body不会自动响应@AppStorage的变化——计算属性的变更无法触发WindowGroup的视图刷新,导致已打开的文档窗口感知不到外观设置的改动。
另外,尝试直接设置environment(\.colorScheme)会报错,因为该环境变量是只读的,必须通过preferredColorScheme指定外观模式。
解决方案
通过枚举管理外观状态+自定义环境键,确保所有窗口都能响应外观设置的变化:
1. 修改MyApp.swift
import SwiftUI @main struct darkSpiritApp: App { // 用枚举rawValue存储,确保状态变化可被监听 @AppStorage("appearanceMode") private var appearanceModeRawValue: String = AppearanceMode.system.rawValue private var appearanceMode: AppearanceMode { AppearanceMode(rawValue: appearanceModeRawValue) ?? .system } var body: some Scene { WindowGroup { ContentView() .preferredColorScheme(appearanceMode.colorScheme) .environment(\.appearanceMode, appearanceMode) } Settings { PreferencesView() .preferredColorScheme(appearanceMode.colorScheme) .environment(\.appearanceMode, appearanceMode) } } } // 统一管理外观模式的枚举 enum AppearanceMode: String, CaseIterable, Identifiable { case system = "System" case light = "Light" case dark = "Dark" var id: String { rawValue } var colorScheme: ColorScheme? { switch self { case .system: return nil // 跟随系统 case .light: return .light case .dark: return .dark } } } // 自定义环境键,让所有视图能监听外观模式变化 struct AppearanceModeKey: EnvironmentKey { static let defaultValue: AppearanceMode = .system } extension EnvironmentValues { var appearanceMode: AppearanceMode { get { self[AppearanceModeKey.self] } set { self[AppearanceModeKey.self] = newValue } } }
2. 修改AppearanceSelectionView
struct AppearanceSelectionView: View { @AppStorage("appearanceMode") private var appearanceModeRawValue: String = AppearanceMode.system.rawValue // 绑定枚举类型,避免魔法数字 private var appearanceMode: Binding<AppearanceMode> { Binding( get: { AppearanceMode(rawValue: appearanceModeRawValue) ?? .system }, set: { appearanceModeRawValue = $0.rawValue } ) } var body: some View { Picker(selection: appearanceMode) { Text("System").tag(AppearanceMode.system) Text("Light").tag(AppearanceMode.light) Text("Dark").tag(AppearanceMode.dark) } label: { Text("Select Appearance") } .pickerStyle(.menu) } }
3. 优化ContentView(可选)
如果需要确保视图强制刷新,可添加onChange监听外观模式变化:
struct ContentView: View { @Environment(\.appearanceMode) private var appearanceMode var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } .padding() .onChange(of: appearanceMode) { _ in // 触发视图刷新,多数情况下preferredColorScheme已足够 } } }
原理说明
- 用枚举替代数字存储外观设置,代码更清晰且避免错误
@AppStorage("appearanceMode")的变化会触发darkSpiritApp的body重新计算,从而更新所有窗口的preferredColorScheme- 自定义环境键让所有视图能感知外观模式变化,确保已打开的窗口及时刷新
内容的提问来源于stack exchange,提问作者iPadawan
相关产品推荐
相关产品推荐

