diff --git a/V2er/General/RootView.swift b/V2er/General/RootView.swift index 5cc1ef4..8b90fa2 100644 --- a/V2er/General/RootView.swift +++ b/V2er/General/RootView.swift @@ -27,6 +27,49 @@ class RootHostingController: UIHostingController { override var preferredStatusBarStyle: UIStatusBarStyle { return V2erApp.statusBarState } + + override func viewDidLoad() { + super.viewDidLoad() + // Apply the saved appearance mode + if let savedMode = UserDefaults.standard.string(forKey: "appearanceMode") { + applyAppearanceFromString(savedMode) + } + + // Listen for appearance changes + NotificationCenter.default.addObserver( + self, + selector: #selector(handleAppearanceChange), + name: NSNotification.Name("AppearanceDidChange"), + object: nil + ) + } + + @objc private func handleAppearanceChange(_ notification: Notification) { + if let appearance = notification.object as? AppearanceMode { + applyAppearanceFromString(appearance.rawValue) + } + } + + func applyAppearanceFromString(_ modeString: String) { + let style: UIUserInterfaceStyle + switch modeString { + case "light": + style = .light + case "dark": + style = .dark + default: + style = .unspecified + } + overrideUserInterfaceStyle = style + + // Force the view to redraw + view.setNeedsDisplay() + view.setNeedsLayout() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } } extension View { diff --git a/V2er/General/V2erApp.swift b/V2er/General/V2erApp.swift index f35d97b..9d13efc 100644 --- a/V2er/General/V2erApp.swift +++ b/V2er/General/V2erApp.swift @@ -7,6 +7,7 @@ // import SwiftUI +import Combine @main struct V2erApp: App { @@ -32,63 +33,155 @@ struct V2erApp: App { RootHostView() .environmentObject(store) .preferredColorScheme(store.appState.settingState.appearance.colorScheme) - .onAppear { - updateNavigationBarAppearance(for: store.appState.settingState.appearance) - updateWindowInterfaceStyle(for: store.appState.settingState.appearance) - } - .onChange(of: store.appState.settingState.appearance) { newValue in - updateNavigationBarAppearance(for: newValue) - updateWindowInterfaceStyle(for: newValue) - } - } - } + } .onAppear { + updateAppearance(store.appState.settingState.appearance) + } .onChange(of: store.appState.settingState.appearance) { newValue in + updateAppearance(newValue) + } } + } + + private func updateAppearance(_ appearance: AppearanceMode) { + updateNavigationBarAppearance(for: appearance) + updateWindowInterfaceStyle(for: appearance) + } + + static func updateAppearanceStatic(_ appearance: AppearanceMode) { + updateNavigationBarAppearanceStatic(for: appearance) + updateWindowInterfaceStyleStatic(for: appearance) } + static func updateNavigationBarAppearanceStatic(for appearance: AppearanceMode) { + DispatchQueue.main.async { + let navbarAppearance = UINavigationBarAppearance() + + // Determine if we should use dark mode + let isDarkMode: Bool + switch appearance { + case .light: + isDarkMode = false + case .dark: + isDarkMode = true + case .system: + isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark + } + let tintColor = isDarkMode ? UIColor.white : UIColor.black + navbarAppearance.titleTextAttributes = [.foregroundColor: tintColor] + navbarAppearance.largeTitleTextAttributes = [.foregroundColor: tintColor] + navbarAppearance.backgroundColor = .clear + + let navAppearance = UINavigationBar.appearance() + navAppearance.standardAppearance = navbarAppearance + navAppearance.compactAppearance = navbarAppearance + navAppearance.scrollEdgeAppearance = navbarAppearance + navAppearance.backgroundColor = .clear + navAppearance.tintColor = tintColor + + // Force refresh of current navigation controllers + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + windowScene.windows.forEach { window in + window.subviews.forEach { _ in + window.tintColor = tintColor + } } } } + } + private func updateNavigationBarAppearance(for appearance: AppearanceMode) { - let navbarAppearance = UINavigationBarAppearance() - - // Determine if we should use dark mode - let isDarkMode: Bool - switch appearance { - case .light: - isDarkMode = false - case .dark: - isDarkMode = true - case .system: - isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark - } - - let tintColor = isDarkMode ? UIColor.white : UIColor.black - navbarAppearance.titleTextAttributes = [.foregroundColor: tintColor] - navbarAppearance.largeTitleTextAttributes = [.foregroundColor: tintColor] - navbarAppearance.backgroundColor = .clear - - let navAppearance = UINavigationBar.appearance() - navAppearance.standardAppearance = navbarAppearance - navAppearance.compactAppearance = navbarAppearance - navAppearance.scrollEdgeAppearance = navbarAppearance - navAppearance.backgroundColor = .clear - navAppearance.tintColor = tintColor + DispatchQueue.main.async { + let navbarAppearance = UINavigationBarAppearance() + + // Determine if we should use dark mode + let isDarkMode: Bool + switch appearance { + case .light: + isDarkMode = false + case .dark: + isDarkMode = true + case .system: + isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark + } + let tintColor = isDarkMode ? UIColor.white : UIColor.black + navbarAppearance.titleTextAttributes = [.foregroundColor: tintColor] + navbarAppearance.largeTitleTextAttributes = [.foregroundColor: tintColor] + navbarAppearance.backgroundColor = .clear + + let navAppearance = UINavigationBar.appearance() + navAppearance.standardAppearance = navbarAppearance + navAppearance.compactAppearance = navbarAppearance + navAppearance.scrollEdgeAppearance = navbarAppearance + navAppearance.backgroundColor = .clear + navAppearance.tintColor = tintColor + + // Force refresh of current navigation controllers + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + windowScene.windows.forEach { window in + window.subviews.forEach { _ in + window.tintColor = tintColor + } } } } } + static func updateWindowInterfaceStyleStatic(for appearance: AppearanceMode) { + DispatchQueue.main.async { + let style: UIUserInterfaceStyle + switch appearance { + case .light: + style = .light + case .dark: + style = .dark + case .system: + style = .unspecified + } + + // Update all connected scenes + UIApplication.shared.connectedScenes.forEach { scene in + if let windowScene = scene as? UIWindowScene { + windowScene.windows.forEach { window in + window.overrideUserInterfaceStyle = style + } } } + // Also update the stored window if available + if let window = V2erApp.window { + window.overrideUserInterfaceStyle = style + } + // Update the root hosting controller + if let rootHostingController = V2erApp.rootViewController as? RootHostingController { + rootHostingController.applyAppearanceFromString(appearance.rawValue) + } + // Force a redraw + UIApplication.shared.connectedScenes.forEach { scene in + if let windowScene = scene as? UIWindowScene { + windowScene.windows.forEach { $0.setNeedsDisplay() } + } } } + } + private func updateWindowInterfaceStyle(for appearance: AppearanceMode) { - // Get all windows and update their interface style - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return } - - let style: UIUserInterfaceStyle - switch appearance { - case .light: - style = .light - case .dark: - style = .dark - case .system: - style = .unspecified - } - - // Update all windows in the scene - windowScene.windows.forEach { window in - window.overrideUserInterfaceStyle = style - } + DispatchQueue.main.async { + let style: UIUserInterfaceStyle + switch appearance { + case .light: + style = .light + case .dark: + style = .dark + case .system: + style = .unspecified + } + + // Update all connected scenes + UIApplication.shared.connectedScenes.forEach { scene in + if let windowScene = scene as? UIWindowScene { + windowScene.windows.forEach { window in + window.overrideUserInterfaceStyle = style + } } } + // Also update the stored window if available + if let window = V2erApp.window { + window.overrideUserInterfaceStyle = style + } + // Update the root hosting controller + if let rootHostingController = V2erApp.rootViewController as? RootHostingController { + rootHostingController.applyAppearanceFromString(appearance.rawValue) + } + // Force a redraw + UIApplication.shared.connectedScenes.forEach { scene in + if let windowScene = scene as? UIWindowScene { + windowScene.windows.forEach { $0.setNeedsDisplay() } + } } } } static func changeStatusBarStyle(_ style: UIStatusBarStyle) { diff --git a/V2er/State/DataFlow/Reducers/SettingReducer.swift b/V2er/State/DataFlow/Reducers/SettingReducer.swift index ec3613e..eccd661 100644 --- a/V2er/State/DataFlow/Reducers/SettingReducer.swift +++ b/V2er/State/DataFlow/Reducers/SettingReducer.swift @@ -11,17 +11,22 @@ import Foundation func settingStateReducer(_ state: SettingState, _ action: Action) -> (SettingState, Action?) { var state = state var followingAction: Action? = action - + switch action { case let action as SettingActions.ChangeAppearanceAction: state.appearance = action.appearance // Save to UserDefaults UserDefaults.standard.set(action.appearance.rawValue, forKey: "appearanceMode") + UserDefaults.standard.synchronize() + + // Post notification for immediate UI update + NotificationCenter.default.post(name: NSNotification.Name("AppearanceDidChange"), object: action.appearance) + followingAction = nil default: break } - + return (state, followingAction) } diff --git a/V2er/State/DataFlow/State/SettingState.swift b/V2er/State/DataFlow/State/SettingState.swift index 29b6e58..97a3ee5 100644 --- a/V2er/State/DataFlow/State/SettingState.swift +++ b/V2er/State/DataFlow/State/SettingState.swift @@ -11,7 +11,7 @@ import SwiftUI struct SettingState: FluxState { var appearance: AppearanceMode = .system - + init() { // Load saved preference if let savedMode = UserDefaults.standard.string(forKey: "appearanceMode"), diff --git a/V2er/View/Settings/AppearanceSettingView.swift b/V2er/View/Settings/AppearanceSettingView.swift index a531739..fcd7791 100644 --- a/V2er/View/Settings/AppearanceSettingView.swift +++ b/V2er/View/Settings/AppearanceSettingView.swift @@ -10,16 +10,13 @@ import SwiftUI struct AppearanceSettingView: View { @EnvironmentObject private var store: Store - @State private var selectedAppearance: AppearanceMode = .system - + var body: some View { formView .navBar("外观设置") - .onAppear { - selectedAppearance = store.appState.settingState.appearance - } } + @ViewBuilder private var formView: some View { ScrollView { @@ -35,14 +32,13 @@ struct AppearanceSettingView: View { VStack(spacing: 0) { ForEach(AppearanceMode.allCases, id: \.self) { mode in Button(action: { - selectedAppearance = mode dispatch(SettingActions.ChangeAppearanceAction(appearance: mode)) }) { HStack { Text(mode.displayName) .foregroundColor(.primaryText) Spacer() - if selectedAppearance == mode { + if store.appState.settingState.appearance == mode { Image(systemName: "checkmark") .foregroundColor(.tintColor) .font(.system(size: 14, weight: .semibold))