16

我目前正在我的应用程序中查看暗模式。虽然由于我的 SwiftUI 基础,暗模式本身并没有太大的困难,但我正在努力设置独立于系统 ColorScheme 的 ColorScheme 的选项。

我在苹果人机界面指南中发现了这一点,我想实现这个功能。(链接:人机界面指南

知道如何在 SwiftUI 中执行此操作吗?我找到了一些提示,@Environment但没有关于这个主题的更多信息。(链接:最后一段

4

6 回答 6

33

单视图

要更改单个视图的配色方案(可能是ContentView应用程序的主视图),您可以使用以下修饰符:

.environment(\.colorScheme, .light) // or .dark

或者

.preferredColorScheme(.dark)

此外,您可以将其应用于ContentView使您的整个应用程序变暗!

假设您没有更改ContentView场景委托中的名称或@main


整个应用程序(包括UIKit部分和SwiftUI

UserInterfaceStyle首先,您需要访问窗口以更改调用的应用程序 colorScheme UIKit

我用这个SceneDelegate

private(set) static var shared: SceneDelegate?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    Self.shared = self
    ...
}

然后你需要将一个动作绑定到切换。所以你需要一个模型。

struct ToggleModel {
    var isDark: Bool = true {
        didSet { 
            SceneDelegate.shared?.window!.overrideUserInterfaceStyle = isDark ? .dark : .light 
        }
    }
}

最后,您只需要切换开关:

struct ContentView: View {
     @State var model = ToggleModel()

     var body: some View {
         Toggle(isOn: $model.isDark) {
             Text("is Dark")
        }
    }
}

来自应用程序的 UIKit 部分

每个UIView人都可以访问窗口,因此您可以使用它将. overrideUserInterfaceStyle值设置为您需要的任何方案。

myView.window?.overrideUserInterfaceStyle = .dark
于 2019-10-20T19:20:02.587 回答
7

@AppStorage用于切换暗模式的演示

PS:全局切换需要在WindowGroup/MainContentView中添加修饰符

import SwiftUI

struct SystemColor: Hashable {
    var text: String
    var color: Color
}

let backgroundColors: [SystemColor] = [.init(text: "Red", color: .systemRed), .init(text: "Orange", color: .systemOrange), .init(text: "Yellow", color: .systemYellow), .init(text: "Green", color: .systemGreen), .init(text: "Teal", color: .systemTeal), .init(text: "Blue", color: .systemBlue), .init(text: "Indigo", color: .systemIndigo), .init(text: "Purple", color: .systemPurple), .init(text: "Pink", color: .systemPink), .init(text: "Gray", color: .systemGray), .init(text: "Gray2", color: .systemGray2), .init(text: "Gray3", color: .systemGray3), .init(text: "Gray4", color: .systemGray4), .init(text: "Gray5", color: .systemGray5), .init(text: "Gray6", color: .systemGray6)]

struct DarkModeColorView: View {

    @AppStorage("isDarkMode") var isDarkMode: Bool = true

    var body: some View {
        Form {
            Section(header: Text("Common Colors")) {
                ForEach(backgroundColors, id: \.self) {
                    ColorRow(color: $0)
                }
            }
        }
        .toolbar {
            ToolbarItem(placement: .principal) { // navigation bar
               Picker("Color", selection: $isDarkMode) {
                    Text("Light").tag(false)
                    Text("Dark").tag(true)
                }
                .pickerStyle(SegmentedPickerStyle())
            }
        }
        .modifier(DarkModeViewModifier())
    }
}

private struct ColorRow: View {

    let color: SystemColor

    var body: some View {
        HStack {
            Text(color.text)
            Spacer()
            Rectangle()
                .foregroundColor(color.color)
                .frame(width: 30, height: 30)
        }
    }
}

public struct DarkModeViewModifier: ViewModifier {

    @AppStorage("isDarkMode") var isDarkMode: Bool = true

    public func body(content: Content) -> some View {
        content
            .environment(\.colorScheme, isDarkMode ? .dark : .light)
            .preferredColorScheme(isDarkMode ? .dark : .light) // tint on status bar
    }
}

struct DarkModeColorView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            DarkModeColorView()
        }
    }
}

在此处输入图像描述

于 2020-07-20T07:45:40.113 回答
7

@Mojtaba Hosseini 的回答确实帮助了我,但我使用的是 iOS14@main而不是SceneDelegate,以及一些UIKit视图,所以我最终使用了这样的东西(这不会切换模式,但它确实设置了暗模式SwiftUIUIKit

@main
struct MyTestApp: App {

    @Environment(\.scenePhase) private var phase

    var body: some Scene {
        WindowGroup {
            ContentView()
                .accentColor(.red)
                .preferredColorScheme(.dark)
        }
        .onChange(of: phase) { _ in
            setupColorScheme()
        }
    }

    private func setupColorScheme() {
        // We do this via the window so we can access UIKit components too.
        let window = UIApplication.shared.windows.first
        window?.overrideUserInterfaceStyle = .dark
        window?.tintColor = UIColor(Color.red)
    }
}
于 2021-02-12T05:35:25.613 回答
3

@ADB 的答案很好,但我找到了一个更好的答案。希望有人找到比我更好的:D 一旦应用程序切换状态(进入后台并返回),这种方法不会一遍又一遍地调用相同的函数

在您@main看来添加:

ContentView()
    .modifier(DarkModeViewModifier())

现在创建DarkModeViewModifier()ViewModel:

class AppThemeViewModel: ObservableObject {
    
    @AppStorage("isDarkMode") var isDarkMode: Bool = true                           // also exists in DarkModeViewModifier()
    @AppStorage("appTintColor") var appTintColor: AppTintColorOptions = .indigo
    
}

struct DarkModeViewModifier: ViewModifier {
    @ObservedObject var appThemeViewModel: AppThemeViewModel = AppThemeViewModel()
    
    public func body(content: Content) -> some View {
        content
            .preferredColorScheme(appThemeViewModel.isDarkMode ? .dark : appThemeViewModel.isDarkMode == false ? .light : nil)
            .accentColor(Color(appThemeViewModel.appTintColor.rawValue))
    }
}
于 2021-09-08T20:20:12.657 回答
2

使用 SwiftUI 和 SceneDelegate 生命周期的全系统

我使用Mojtaba Hosseini 在答案中提供的提示在 SwiftUI(具有 AppDelegate 生命周期的应用程序)中制作了我自己的版本。我还没有考虑使用 iOS14 的 @main 代替 SceneDelegate。

这是 GitHub 存储库的链接。该示例具有浅色、深色和自动选择器,可更改整个应用程序的设置。
我加倍努力使其可本地化!

GitHub 仓库

我需要访问SceneDelegate并使用与 Mustapha 相同的代码,并添加少量内容,当应用程序启动时,我需要读取存储在 UserDefaults 或 @AppStorage 等中的设置。
因此,我在启动时再次更新 UI:

private(set) static var shared: SceneDelegate?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    Self.shared = self

    // this is for when the app starts - read from the user defaults
    updateUserInterfaceStyle()
}

该功能updateUserInterfaceStyle()将在SceneDelegate. 我在这里使用了 UserDefaults 的扩展来使其与 iOS13 兼容(感谢twanni!):

func updateUserInterfaceStyle() {
        DispatchQueue.main.async {
            switch UserDefaults.userInterfaceStyle {
            case 0:
                self.window?.overrideUserInterfaceStyle = .unspecified
            case 1:
                self.window?.overrideUserInterfaceStyle = .light
            case 2:
                self.window?.overrideUserInterfaceStyle = .dark
            default:
                self.window?.overrideUserInterfaceStyle = .unspecified
            }
        }
    }

这与苹果文档一致UIUserInterfaceStyle

使用选择器意味着我需要对我的三个案例进行迭代,所以我创建了一个符合可识别并且是LocalizedStringKey本地化类型的枚举:

// check LocalizedStringKey instead of string for localisation!
enum Appearance: LocalizedStringKey, CaseIterable, Identifiable {
    case light
    case dark
    case automatic

    var id: String { UUID().uuidString }
}

这是选择器的完整代码:


struct AppearanceSelectionPicker: View {
    @Environment(\.colorScheme) var colorScheme
    @State private var selectedAppearance = Appearance.automatic

    var body: some View {
        HStack {
            Text("Appearance")
                .padding()
                .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
            Picker(selection: $selectedAppearance, label: Text("Appearance"))  {
                ForEach(Appearance.allCases) { appearance in
                    Text(appearance.rawValue)
                        .tag(appearance)
                }
            }
            .pickerStyle(WheelPickerStyle())
            .frame(width: 150, height: 50, alignment: .center)
            .padding()
            .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
        }
        .padding()

        .onChange(of: selectedAppearance, perform: { value in
            print("changed to ", value)
            switch value {
                case .automatic:
                    UserDefaults.userInterfaceStyle = 0
                    SceneDelegate.shared?.window?.overrideUserInterfaceStyle =  .unspecified
                case .light:
                    UserDefaults.userInterfaceStyle = 1
                    SceneDelegate.shared?.window?.overrideUserInterfaceStyle =  .light
                case .dark:
                    UserDefaults.userInterfaceStyle = 2
                    SceneDelegate.shared?.window?.overrideUserInterfaceStyle =  .dark
            }
        })
        .onAppear {
            print(colorScheme)
            print("UserDefaults.userInterfaceStyle",UserDefaults.userInterfaceStyle)
            switch UserDefaults.userInterfaceStyle {
                case 0:
                    selectedAppearance = .automatic
                case 1:
                    selectedAppearance = .light
                case 2:
                    selectedAppearance = .dark
                default:
                    selectedAppearance = .automatic
            }
        }
    }
}

onAppear当用户进入该设置视图时,该代码用于将滚轮设置为正确的值。每次移动轮子时,通过.onChange修改器,用户默认值都会更新,并且应用程序通过其对SceneDelegate.

(如果有兴趣,可以在 GH repo 上看到 gif。)

于 2021-11-12T10:05:55.873 回答
1
#SwiftUI #iOS #DarkMode #ColorScheme

//you can take one boolean and set colorScheme of perticuler view accordingly such like below

struct ContentView: View {

    @State var darkMode : Bool =  false

    var body: some View {
        VStack {
         Toggle("DarkMode", isOn: $darkMode)
            .onTapGesture(count: 1, perform: {
                darkMode.toggle()
            })
        }
        .preferredColorScheme(darkMode ? .dark : .light)

    }
}



// you can also set dark light mode of whole app such like below 

struct ContentView: View {
    @State var darkMode : Bool =  false

    var body: some View {
        VStack {
         Toggle("DarkMode", isOn: $darkMode)
            .onTapGesture(count: 1, perform: {
                darkMode.toggle()
            })
        }
        .onChange(of: darkMode, perform: { value in
            SceneDelegate.shared?.window?.overrideUserInterfaceStyle = value ? .dark : .light
        })

    }
}
于 2021-08-22T12:07:58.910 回答