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

SwiftUI应用实现系统/浅色/深色模式实时切换适配iOS15+

iOS 15+ SwiftUI 实时切换系统/浅色/深色外观实现方案

旧教程中提供的单窗口样式修改方案,在iOS 15及以上版本因系统多场景管理机制变更无法实时生效,核心问题是没有覆盖所有激活状态的窗口实例,以下是可直接落地的兼容方案:

核心实现步骤

  • 第一步:定义外观模式枚举与映射规则
    枚举统一管理三种模式的展示文案、存储值、对应UIKit样式属性,方便后续统一维护:
    enum AppAppearance: Int, CaseIterable, Identifiable {
        case system = 0
        case light = 1
        case dark = 2
        
        var id: Int { rawValue }
        
        var displayName: String {
            switch self {
            case .system: return "跟随系统"
            case .light: return "浅色模式"
            case .dark: return "深色模式"
            }
        }
        
        var uiStyle: UIUserInterfaceStyle {
            switch self {
            case .system: return .unspecified
            case .light: return .light
            case .dark: return .dark
            }
        }
    }
    
  • 第二步:创建全局可观测的外观管理单例
    核心适配点:切换样式时遍历当前所有激活场景下的全部window更新属性,保证所有页面实时刷新,同时通过@AppStorage持久化用户选择:
    import SwiftUI
    
    class AppearanceManager: ObservableObject {
        @AppStorage("app_selected_appearance") private var storedValue: Int = AppAppearance.system.rawValue
        @Published var currentAppearance: AppAppearance
        
        static let shared = AppearanceManager()
        
        private init() {
            currentAppearance = AppAppearance(rawValue: storedValue) ?? .system
        }
        
        func switchTo(_ appearance: AppAppearance) {
            currentAppearance = appearance
            storedValue = appearance.rawValue
            
            // 兼容iOS15+多场景:遍历所有激活的window场景更新样式
            guard let allScenes = UIApplication.shared.connectedScenes as? Set<UIWindowScene> else { return }
            allScenes.forEach { scene in
                scene.windows.forEach { window in
                    window.overrideUserInterfaceStyle = appearance.uiStyle
                }
            }
        }
    }
    
  • 第三步:应用入口注入配置,冷启动自动加载保存的样式
    在App结构体的初始化方法中提前应用已保存的样式,避免冷启动闪屏,同时将管理类注入全局环境:
    @main
    struct CustomAppearanceApp: App {
        @StateObject private var manager = AppearanceManager.shared
        
        init() {
            // 启动时立刻应用用户保存的样式
            AppearanceManager.shared.switchTo(AppearanceManager.shared.currentAppearance)
        }
        
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(manager)
            }
        }
    }
    
  • 第四步:设置页添加切换入口
    绑定选择器的值,用户选择后立刻调用切换方法实现实时更新:
    struct SettingPage: View {
        @EnvironmentObject private var manager: AppearanceManager
        
        var body: some View {
            Form {
                Section("外观设置") {
                    Picker("界面模式", selection: Binding(
                        get: { manager.currentAppearance },
                        set: { newMode in
                            manager.switchTo(newMode)
                        }
                    )) {
                        ForEach(AppAppearance.allCases) { mode in
                            Text(mode.displayName).tag(mode)
                        }
                    }
                    .pickerStyle(.inline)
                }
            }
            .navigationTitle("设置")
        }
    }
    

避坑说明

  • 不要使用根视图的.preferredColorScheme修饰符实现切换,该修饰符在iOS15+存在样式优先级冲突,系统样式变动时会覆盖用户手动选择的配置
  • 不要只修改UIApplication.shared.windows.first的样式,iOS15+该属性返回的窗口列表不全,多窗口场景下会出现部分页面不更新的问题
  • 不要将样式切换逻辑写在视图的.onAppear中,会导致应用切后台返回时样式被重置

内容的提问来源于stack exchange,提问作者user17745324

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:48:22