You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 21:20:09