SwiftUI — 系统主题

下面我会为你提供关于 系统主题(System Appearance) 的一篇成体系的内容,涵盖 iOS 和 macOS 中的 深色模式(Dark Mode)浅色模式(Light Mode),以及相关概念、开发注意事项、最佳实践等内容。


系统主题概览:浅色模式和深色模式 #


1. 什么是系统主题? #

系统主题是指操作系统的整体视觉外观风格,包括配色、阴影、对比度、透明度和光影效果等。自 iOS 13macOS 10.14(Mojave) 起,Apple 引入了 深色模式(Dark Mode),让用户能够选择使用浅色(Light Mode)或深色(Dark Mode),或根据时间自动切换系统主题。

  • 浅色模式(Light Mode): 系统界面和应用程序主要以白色或浅色为背景,深色为前景(如文本和图标),适合大多数环境下的使用,尤其是亮光环境。

  • 深色模式(Dark Mode): 系统界面和应用程序主要以深色为背景,浅色为前景,适用于暗光环境或夜间使用,以减少眼睛疲劳,同时突出内容。


2. 系统主题的特性 #

  • 在启用深色模式时,系统会切换通用设计的颜色样式(例如窗口背景、桌面图标、菜单栏等)。
  • 开发者可以通过系统提供的 API,自适应用户当前环境,实现自动切换主题。
  • 系统主题切换会:
    • 动态改变界面颜色。
    • 影响 Apple 提供的系统控件,例如按钮、导航栏等。
    • 自动调整内容的对比度和字体样式。

3. 开发者如何实现系统主题适配 #

在开发中,我们需要确保应用能够自动适配系统主题,或在特定场景下支持自定义主题。以下分为 SwiftUI 和 UIKit 两大框架,分别讲解如何处理系统主题。


3.1 SwiftUI 中的主题适配 #

(1) 默认支持系统主题 #

SwiftUI 应用中的大部分 UI 默认支持系统主题切换。只需要确保:

  1. 不使用硬编码的颜色,而是使用系统的 动态颜色(Dynamic Colors)
  2. 避免对背景和前景指定固定值。

代码示例:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
                .padding()
            Button("Press Me") {}
                .padding()
        }
        .background(Color(.systemBackground)) // 自动适配系统主题
    }
}

说明:

  • Color(.systemBackground) 将使用系统动态背景色,浅色模式背景为白色,深色模式背景为黑色。
  • 系统控件(如 Button)和字体颜色也会根据主题自动变化。

(2) 强制设置颜色模式(preferredColorScheme 参数) #

如果需要在某些页面强制应用浅色或深色模式,可以使用 .preferredColorScheme 修饰符。

示例:强制深色模式显示

import SwiftUI

struct DarkModeView: View {
    var body: some View {
        Text("This View is Always Dark Mode")
            .padding()
            .preferredColorScheme(.dark) // 强制深色模式
    }
}

(3) 检测系统当前主题(colorScheme 环境变量) #

SwiftUI 提供了 Environment 属性包装器,允许你检测当前的系统主题。

代码示例:检测并响应系统主题

import SwiftUI

struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme // 当前的颜色模式 ("light" or "dark")
    
    var body: some View {
        VStack {
            if colorScheme == .dark {
                Text("Dark Mode Enabled")
            } else {
                Text("Light Mode Enabled")
            }
        }
        .padding()
    }
}

3.2 UIKit 中的主题适配 #

(1) 动态颜色(UIColorCGColor #

UIKit 提供了一组动态适配颜色的 API,当系统主题切换时,这些动态颜色会和系统主题保持一致。

常用动态颜色:

  • UIColor.systemBackground
  • UIColor.secondarySystemBackground
  • UIColor.label
  • UIColor.secondaryLabel
  • UIColor.systemGray

代码示例:动态颜色使用

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor.systemBackground
        
        let label = UILabel()
        label.text = "Hello, World!"
        label.textColor = UIColor.label // 自动根据系统主题切换
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

(2) 检测系统外观模式(UITraitCollection #

UIKit 中可以通过 traitCollection 获取当前系统的外观模式。

示例:检测当前主题模式

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    
    if traitCollection.userInterfaceStyle == .dark {
        print("Dark Mode")
    } else {
        print("Light Mode")
    }
}

3.3 动态图片资源 #

  • Assets.xcassets 文件中,你可以为图片资源设置不同的外观配置(例如深色和浅色模式下的不同图片)。
  • 在项目的 Assets 文件中设置「Appearance」为 Any, Light, Dark。

使用动态图片:

Image("DynamicImage")
    .resizable()

当系统切换主题时,图片会自动切换到相应的版本。


4. 用户自定义主题实现 #

除了随系统主题切换,很多应用希望提供用户自定义主题功能(例如手动切换深浅模式),这需要保存用户的选择并动态指定视图的外观样式。

以下是如何实现用户自定义主题的示例:


4.1 使用 @AppStorage 持久化用户选择(SwiftUI) #

代码示例:主题选择器

struct ContentView: View {
    @AppStorage("userPreferredTheme") var userPreferredTheme: String = "system"
    
    var colorScheme: ColorScheme? {
        switch userPreferredTheme {
        case "light": return .light
        case "dark": return .dark
        default: return nil // 跟随系统
        }
    }

    var body: some View {
        VStack {
            Text("Select Theme")
            Picker("", selection: $userPreferredTheme) {
                Text("System").tag("system")
                Text("Light").tag("light")
                Text("Dark").tag("dark")
            }
            .pickerStyle(SegmentedPickerStyle())
        }
        .preferredColorScheme(colorScheme)
    }
}

4.2 保存用户偏好并手动切换(UIKit) #

UIKit 中可以使用 UserDefaults 保存用户的主题偏好,并根据保存的值设置全局外观。

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        if let theme = UserDefaults.standard.string(forKey: "themePreference") {
            overrideUserInterfaceStyle = (theme == "dark") ? .dark : .light
        }
    }

    func saveThemePreference(_ theme: String) {
        UserDefaults.standard.set(theme, forKey: "themePreference")
        overrideUserInterfaceStyle = (theme == "dark") ? .dark : .light
    }
}

5. 最佳实践 #

5.1 始终使用动态颜色 #

  • 避免使用硬编码的颜色(如 .black.white)。
  • 使用系统提供的动态颜色(UIColorColor),以确保在主题切换时界面自动适配。

5.2 提供用户主题选项 #

  • 允许用户手动选择浅色/深色模式,这样可以覆盖系统设置(如果业务需求明确)。

5.3 测试界面在不同主题下的表现 #

  • 确保在浅色和深色两种模式下,界面都保持良好的可读性和对比度。
  • 可以通过 preferredColorScheme 或开发者模拟器调试。

6. 总结 #

系统主题是现代 UI 中的一个重要组成,提供了动态的用户体验能力,而开发者对浅色和深色模式的适配主要关注:

  1. 使用动态颜色和图片资源替代硬编码颜色。
  2. 为用户提供个性化主题切换选项
  3. 在浅色和深色主题下测试和优化可读性和对比度

这不仅能提升用户体验,还能为应用带来更广泛的适配能力,让你的应用更符合现代设计要求。

本文共 1989 字,上次修改于 Jan 8, 2025