SwiftUI 开发之旅:适配深色模式

4,685 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

大家好,我是 new_cheng

我们在开发 swiftui 的时候,虽然 swiftui 默认支持深色模式,对于一些基本的视图,比如文字,背景都有默认的深色模式样式。

但是为了提高用户体验,一般还需要适配深色模式。而对于深色模式的适配,不应在开发完应用后,再去考虑往应用中加入深色模式,应该在开发的初期就做好规划,在开发过程中就可以边开发边测试深色模式的显示效果。

那既然要适配深色模式,在 swiftui 中,我们该采用什么方式来做呢?

有 2 种常用的的编码方案可以去适配深色模式。

1. UIColor 适配深色模式

可以通过扩展 UIColor 的方式来使用颜色,我们只需要简单的桥接 UIColor。

extension UIColor {
  convenience init(light: UIColor, dark: UIColor) {
    self.init { traitCollection in
      switch traitCollection.userInterfaceStyle {
      case .light, .unspecified:
        return light
      case .dark:
        return dark
      @unknown default:
        return light
      }
    }
  }
}

extension Color {
  // 再定义一个颜色
  static let defaultBackground = Color(light: .white, dark: .black)
 
  init(light: Color, dark: Color) {
    self.init(UIColor(light: UIColor(light), dark: UIColor(dark)))
  }
}
// 使用
Text("文字").foregroundColor(Color.defaultBackground)

2. 使用 Assets.xcassets 适配深色模式

Assets.xcassets 允许我们定义 Color Set,一个 Color Set 包含深色模式和浅色模式所显示的颜色,我们可以定义多个 Color Set,比如文本,背景,图表等等;通过在 UI 中使用不同的颜色就能实现深色模式的适配,下面来看看具体的操作。

定义 Color Set

在 swiftui 项目选择 -> Assets.xcassets -> 右键 -> New Color Se -> 设置名称

image.png

左边的颜色(Any Appearance)默认就是浅色模式显示的颜色,右边如图所示就是深色模式显示的颜色。

选择颜色,我们就可以设置颜色的具体色值,Xcode 提供了几种颜色的设置方式,这里选择 RGB 即可。

image.png

在这里,我们定义了一个名为 textColor 的 Color Set,下一步我们会将其应用到代码中。

扩展 Color

为了更方便的使用的我们定义的颜色集,我们可以扩展 Color。

创建一个名为 Color.swift 的文件:

import Foundation
import SwiftUI

public extension Color {

    static let textColor = Color("textColor")
    
}

对于使用 Color 作为参数的修饰符,比如 foregroundColor,background,我们就可以很方便的直接通过颜色名称来使用:

import SwiftUI

struct DetailHome: View {
    var body: some View {
        Text("姓名").foregroundColor(.textColor)
    }
}

xcode 会提供代码编写提示。

在 xcode 的预览中,将颜色模式设置为 dark appearance,就能查看效果。

image.png

3. 固定的颜色模式

当我们不想适配深色模式,我们可以锁死颜色模式,不管系统当前的设置是深色模式还是浅色模式,我们都只固定使用某一种颜色模式。

修改项目的 info.plist 文件,添加一条 Appearance 配置,value 为 Dark,App 将会默认使用深色模式,value 为 Light,将会默认使用浅色模式。

image.png

4. 编码控制深色模式

当你希望在代码中细粒度的控制颜色时,可以通过 colorScheme 环境变量来实现。

import SwiftUI

struct DetailHome: View {
    @Environment(\.colorScheme) var colorScheme: ColorScheme

    var body: some View {
        Text((colorScheme == .dark) ? "暗黑模式" : "浅色模式")
    }
}

image.pngimage.png

5. 手动切换深色模式

想要让用户手动控制应用的颜色模式,我们可以提供一个设置页面。

创建一个 AppSetting.swift 文件,用于检测和存储当前应用显示的颜色模式。

这里我们用到了 UserDefaults,这是用户默认数据库的接口,一般用于存储用户信息、App 设置等基础信息,但不宜用于存储大量数据。

当用户在系统、深色、浅色3个选项之间切换的时候,我们会获取到对应的值并赋值给 darkModeSettings,从而去设置 window.overrideUserInterfaceStyle 来改变应用的颜色模式。

此方式只会影响 window 及其所有子视图和子视图控制器。

import SwiftUI

class AppSetting: ObservableObject {
    @Published var darkModeSettings: Int = UserDefaults.standard.integer(forKey: "darkMode") {
        didSet {
            UserDefaults.standard.set(**self**.darkModeSettings, forKey: "darkMode")
            let scenes = UIApplication.shared.connectedScenes
            let windowScene = scenes.first **as**? UIWindowScene
            let window = windowScene?.windows.first
            switch self.darkModeSettings {
            case 0:
                window?.overrideUserInterfaceStyle = .unspecified
            case 1:
                window?.overrideUserInterfaceStyle = .light
            case 2:
                window?.overrideUserInterfaceStyle = .dark
            default:
                window?.overrideUserInterfaceStyle = .unspecified
            }
        }
    }
}

用户设置深色模式页面:

import SwiftUI

struct MyDarkModel: Identifiable {
    let id: Int
    let image: String
    let name: String
}

struct Test2: View {
    @Environment(\.colorScheme) **var** colorScheme: ColorScheme
    @EnvironmentObject **var** appSettings: AppSetting
    
    let darkModel: [MyDarkModel] = [
        MyDarkModel(id: 0, image: "img_dark_auto", name: "系统"),
        MyDarkModel(id: 1, image: "img_dark_off", name: "浅色"),
        MyDarkModel(id: 2, image: "img_dark_on", name: "深色"),
    ]

    var body: some View {
        VStack() {
            List {
                Section {
                    HStack(spacing: 0) {
                        ForEach(darkModel) { item in
                            Button (action: {
                                // 点击按钮时传递颜色模式的值
                                appSettings.darkModeSettings = item.id
                            }) {
                                if appSettings.darkModeSettings == item.id {
                                    Text(item.name)
                                        .padding(.horizontal, 24)
                                        .padding(.vertical, 12)
                                        .foregroundColor(
                                        appSettings.darkModeSettings == item.id ?
                                        Color.white :
                                        Color.primary)
                                    .background(appSettings.darkModeSettings == item.id ?
                                        Color.blue :
                                        Color.white)
                                    .cornerRadius(20)
                                } else {
                                    Text(item.name)
                                        .padding(.horizontal, 24)
                                        .padding(.vertical, 12)
                                        .foregroundColor(Color.primary)
                                }

                            }
                            .buttonStyle(BorderlessButtonStyle())
                            if item.id < 2 {
                                Spacer()
                            }
                        }
                    }
                    .padding(.horizontal, 4)
                    .padding(.vertical)
                }
                Text((colorScheme == .dark) ? "暗黑模式" : "浅色模式")
            }
        }
    }
}

struct Test2_Previews: PreviewProvider {
    static var previews: some View {
        Test2().environmentObject(AppSetting())
    }
}

image.pngimage.png

总结

通过本文,我们学会了如果去适配深色模式,掌握了 Color Set、overrideUserInterfaceStyle 等使用,还有如何支持用户手动切换颜色模式。对于深色模式的适配,推荐采用 Assets.xcassets 的方式去定义一个完整的颜色集来适配。

这是 SwiftUI 开发之旅专栏的文章,是 swiftui 开发学习的经验总结及实用技巧分享,欢迎关注该专栏,会坚持输出。同时欢迎关注我的个人公众号 @JSHub:提供最新的开发信息速报,优质的技术干货推荐。或是查看我的个人博客:Devcursor

👍点赞:如果有收获和帮助,请点个赞支持一下!

🌟收藏:欢迎收藏文章,随时查看!

💬评论:欢迎评论交流学习,共同进步!