SwiftUI:如何让用户使用“亮”、“暗”和“系统”选项实时设置应用程序外观?

Mal*_*ito 1 color-scheme ios swift swiftui ios-darkmode

我目前正在尝试在应用程序中实现一个解决方案,用户应该能够使用以下选项实时切换应用程序的外观:

  • 系统(应用在设备的 iOS 设置中设置的任何外观)
  • Light(应用 .light 配色方案)
  • 深色(应用 . 深色配色方案)

事实证明,使用 .preferredColorScheme() 设置浅色和深色配色方案非常容易且响应迅速;但是,对于“系统”选项,我还没有找到任何令人满意的解决方案。

我目前的方法如下:

  1. 在 ContentView 中使用 @Environment(.colorScheme) 获取设备配色方案
  2. 创建自定义视图修改器以在任何视图上应用相应的配色方案
  3. 使用“MainView”上的修饰符(这是应用程序的真实内容应该存在的地方)在配色方案之间切换

我的想法是在 ContentView 中嵌入 MainView,这样@Environment(.colorScheme) 就不会被任何应用于 MainView 的 colorScheme 所干扰。

但是,它仍然没有按预期工作:设置明暗外观时,一切都按预期工作。但是,当从亮/暗切换到“系统”时,外观的变化只有在重新启动应用程序后才能看到。然而,预期的行为是外观立即改变。

对此有何想法?

以下是相关的代码片段:

主视图

import SwiftUI

struct MainView: View {

    @AppStorage("selectedAppearance") var selectedAppearance = 0

    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                selectedAppearance = 1
            }) {
                Text("Light")
            }
            Spacer()
            Button(action: {
                selectedAppearance = 2
            }) {
                Text("Dark")
            }
            Spacer()
            Button(action: {
                selectedAppearance = 0
            }) {
                Text("System")
            }
            Spacer()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

内容视图

import SwiftUI

struct ContentView: View {

    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        MainView()
            .modifier(ColorSchemeModifier(colorScheme: colorScheme))
    }
}
Run Code Online (Sandbox Code Playgroud)

“实用程序”

import Foundation
import SwiftUI

struct ColorSchemeModifier: ViewModifier {

    @AppStorage("selectedAppearance") var selectedAppearance: Int = 0
    var colorScheme: ColorScheme

    func body(content: Content) -> some View {
        if selectedAppearance == 2 {
            return content.preferredColorScheme(.dark)
        } else if selectedAppearance == 1 {
            return content.preferredColorScheme(.light)
        } else {
            return content.preferredColorScheme(colorScheme)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

clu*_*der 8

它适用于

\n
.preferredColorScheme(selectedAppearance == 1 ? .light : selectedAppearance == 2 ? .dark : nil)\n
Run Code Online (Sandbox Code Playgroud)\n

iOS 14.5+: \n就是这样。他们让“nil”起作用来重置您的首选颜色方案。

\n

iOS14:

\n

唯一的问题是您必须重新加载应用程序才能使系统正常工作。我可以想到一个解决方法 - 当用户选择 \xe2\x80\x9csystem\xe2\x80\x9d 时,您首先确定当前的 colorScheme 是什么,将其更改为它,然后才将 selectedAppearance 更改为 0。

\n
    \n
  • 用户将立即看到结果,并且下次启动时,\xe2\x80\x99 将成为系统主题。
  • \n
\n

编辑:

\n

这是工作思路:

\n
struct MainView: View {\n    @Binding var colorScheme: ColorScheme\n    @AppStorage("selectedAppearance") var selectedAppearance = 0\n\n    var body: some View {\n        VStack {\n            Spacer()\n            Button(action: {\n                selectedAppearance = 1\n            }) {\n                Text("Light")\n            }\n            Spacer()\n            Button(action: {\n                selectedAppearance = 2\n            }) {\n                Text("Dark")\n            }\n            Spacer()\n            Button(action: {\n                if colorScheme == .light {\n                    selectedAppearance = 1\n                }\n                else {\n                    selectedAppearance = 2\n                }\n                DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {\n                    selectedAppearance = 0\n                }\n            }) {\n                Text("System")\n            }\n            Spacer()\n        }\n    }\n}\n\nstruct ContentView: View {\n    @AppStorage("selectedAppearance") var selectedAppearance = 0\n    @Environment(\\.colorScheme) var colorScheme\n    @State var onAppearColorScheme: ColorScheme = .light //only for iOS<14.5\n\n    var body: some View {\n        MainView(colorScheme: $onAppearColorScheme)\n            .onAppear { onAppearColorScheme = colorScheme } //only for iOS<14.5\n            .preferredColorScheme(selectedAppearance == 1 ? .light : selectedAppearance == 2 ? .dark : nil) }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

有一个小问题 -> 它只能作为 onAppear 而不是 onChange (不知道为什么)

\n


Mal*_*ito 5

我最终使用了以下解决方案,这是对@pgb 给出的答案的轻微改编:

内容视图:

struct ContentView: View {

    @AppStorage("selectedAppearance") var selectedAppearance = 0
    var utilities = Utilities()

    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                selectedAppearance = 1
            }) {
                Text("Light")
            }
            Spacer()
            Button(action: {
                selectedAppearance = 2
            }) {
                Text("Dark")
            }
            Spacer()
            Button(action: {
                selectedAppearance = 0
            }) {
                Text("System")
            }
            Spacer()
        }
        .onChange(of: selectedAppearance, perform: { value in
            utilities.overrideDisplayMode()
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

帮手类

class Utilities {

    @AppStorage("selectedAppearance") var selectedAppearance = 0
    var userInterfaceStyle: ColorScheme? = .dark

    func overrideDisplayMode() {
        var userInterfaceStyle: UIUserInterfaceStyle

        if selectedAppearance == 2 {
            userInterfaceStyle = .dark
        } else if selectedAppearance == 1 {
            userInterfaceStyle = .light
        } else {
            userInterfaceStyle = .unspecified
        }
    
        UIApplication.shared.windows.first?.overrideUserInterfaceStyle = userInterfaceStyle
    }
}
Run Code Online (Sandbox Code Playgroud)