SwiftUI

Persistent Storage

SwiftUI 提供了一系列用于处理持久化存储的工具,旨在简化应用状态的保存与恢复。这些工具涵盖了从简单的用户偏好设置到复杂的 Core Data 数据管理。

SwiftUI 提供了一系列用于处理持久化存储的工具,旨在简化应用状态的保存与恢复。这些工具涵盖了从简单的用户偏好设置到复杂的 Core Data 数据管理。

AppStorage

AppStorage 是一个属性包装器,用于从 UserDefaults 中读取和写入值。它使得管理用户偏好设置变得非常简单,当存储的值发生变化时,视图会自动刷新。

基本用法

使用 @AppStorage 声明属性,指定存储的键(Key)和默认值。

struct SettingsView: View {
    @AppStorage("isDarkMode") private var isDarkMode = false
    @AppStorage("username") private var username = "Guest"
    @AppStorage("fontSize") private var fontSize: Double = 14.0

    var body: some View {
        Form {
            Toggle("Dark Mode", isOn: $isDarkMode)
            TextField("Username", text: $username)
            Slider(value: $fontSize, in: 10...30, label: { Text("Font Size") })
        }
    }
}

支持的类型

AppStorage 支持以下基本类型:

  • Bool
  • Int
  • Double
  • String
  • URL
  • Data
  • 遵循 RawRepresentableRawValueIntString 的枚举
enum Theme: String, CaseIterable, Identifiable {
    case light, dark, system
    var id: Self { self }
}

struct ThemeSettingsView: View {
    @AppStorage("appTheme") private var selectedTheme: Theme = .system

    var body: some View {
        Picker("Theme", selection: $selectedTheme) {
            ForEach(Theme.allCases) { theme in
                Text(theme.rawValue.capitalized).tag(theme)
            }
        }
    }
}

defaultAppStorage

使用 defaultAppStorage(_:) 修饰符可以为视图层级中的 AppStorage 指定一个自定义的 UserDefaults 实例。这在需要与 App Group 共享数据或使用特定的 UserDefaults suite 时非常有用。

let customStore = UserDefaults(suiteName: "group.com.example.myapp")!

ContentView()
    .defaultAppStorage(customStore)

SceneStorage

SceneStorage 是一个属性包装器,用于保存和恢复每个场景(Scene)特有的数据。与 AppStorage 不同,SceneStorage 的数据是依附于特定场景的,当场景被销毁时,数据也会随之销毁(但在应用挂起或终止后重新启动时会尝试恢复)。

它非常适合用于状态恢复(State Restoration),例如保存当前选中的标签页、滚动位置或文本编辑器的内容。

基本用法

struct ContentView: View {
    @SceneStorage("selectedTab") private var selectedTab = 0
    @SceneStorage("noteContent") private var noteContent = ""

    var body: some View {
        TabView(selection: $selectedTab) {
            TextEditor(text: $noteContent)
                .tabItem { Text("Notes") }
                .tag(0)
            
            Text("Settings")
                .tabItem { Text("Settings") }
                .tag(1)
        }
    }
}

注意事项

  • 不要使用 SceneStorage 存储敏感数据或关键业务数据,它仅用于 UI 状态恢复。
  • 数据量应保持轻量。
  • iPadOS 上支持多窗口,每个窗口都有独立的 SceneStorage

Core Data Integration

SwiftUI 提供了与 Core Data 深度集成的工具,使得在视图中展示和操作 Core Data 数据变得直观。

Environment Value

首先,需要将 NSManagedObjectContext 注入到环境中。通常在 App 的入口处完成。

@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

FetchRequest

@FetchRequest 用于从 Core Data 存储中获取数据。它会根据指定的排序描述符(Sort Descriptors)和谓词(Predicate)自动执行查询,并在数据变化时更新视图。

struct ItemListView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        predicate: NSPredicate(format: "isFavorite == true"),
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        List {
            ForEach(items) { item in
                Text(item.timestamp!, formatter: itemFormatter)
            }
            .onDelete(perform: deleteItems)
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)
            try? viewContext.save()
        }
    }
}

SectionedFetchRequest

@SectionedFetchRequest@FetchRequest 的变体,用于支持分段(Sectioned)数据列表。它根据指定的属性对结果进行分组。

struct SectionedItemListView: View {
    @SectionedFetchRequest(
        sectionIdentifier: \.category,
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: SectionedFetchResults<String, Item>

    var body: some View {
        List {
            ForEach(items) { section in
                Section(header: Text(section.id)) {
                    ForEach(section) { item in
                        Text(item.timestamp!, formatter: itemFormatter)
                    }
                }
            }
        }
    }
}

动态配置 FetchRequest

如果需要根据用户输入动态改变查询条件,可以将 FetchRequest 封装在一个独立的视图中,并通过 init 方法初始化它。

struct FilteredList: View {
    @FetchRequest var fetchRequest: FetchedResults<Item>

    init(filter: String) {
        _fetchRequest = FetchRequest<Item>(
            sortDescriptors: [],
            predicate: NSPredicate(format: "name CONTAINS[c] %@", filter)
        )
    }

    var body: some View {
        List(fetchRequest, id: \.self) { item in
            Text(item.name ?? "Unknown")
        }
    }
}

总结

  • AppStorage: 适用于全局的用户偏好设置,底层基于 UserDefaults。
  • SceneStorage: 适用于特定场景的 UI 状态恢复,如多窗口应用中的独立状态。
  • Core Data: 适用于复杂的数据模型和大量数据的持久化,SwiftUI 提供了 @FetchRequest 等工具简化集成。
在 GitHub 上编辑

上次更新于