SwiftUI基础篇CoreData

CoreData

概述

文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,CoreData部分调试,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接

1、如何在SwiftUI中使用CoreData

SwiftUI和CoreData作为Apple软件平台的其中两个重要组成部分,有很好的协同能力,SwiftUI有属性包装器、环境支持等等,这些都是为了确保能以最小的麻烦将Core Data集成到SwiftUI的App中。在SwiftUI之前,在架构角度来看,使用Core Data的方式如下:

  • Apple建议在AppDelegate的级别创建容器,然后根据需要return
  • 一部分人喜欢使用Manager Classes
  • 还有一部分人喜欢完全抽象Core Data,以方便完全转移到Realm或其他可选项

SwiftUI与Core Data的集成是不同的,在启动时,需要创建Core Data容器,然后将其托管对象注入到环境中,然后直接在那里执行Request。以下是四个特定的功能,可以帮你理解我的意思:

  1. NSManagedObject遵守ObservableObject协议,这代表可以将任何对象绑定到用户界面。
  2. 环境中有一个managedObjectContent的key,用于存储Core Data托管对象的上下文。
  3. 然后Xcode的模板将这个context注入到初始的内容视图中。
  4. 有一个@FetchRequest的属性包装器,它使用环境的托管对象上下文来获取request

因此,在应用启动时创建一个托管对象上下文,将其附加到视图的环境中,然后使用@FetchRequest加载数据供App使用。

2、在SwiftUI中如何配置Core Data

如果创建一个新项目,使用的是SwiftUI并需要使用Core Data,Xcode的配置分为四步:

  1. 创建一个空的项目模板,通常以你的项目命名(FFModifier.xcdatamodeld),和一个示例配置
  2. 添加一个Persistence.swift文件,将Core Data整齐的封装在这个地方。
  3. 使用managedObjectContext将上下文注入初始ContentView的environment中。
  4. 创建在ContentView中创建、读取和删除示例数据的示例代码。

这是提供了在SwiftUI中使用CoreData获取request的完整能力。

2.1、创建Main.xcdatamodeld

通过cmd+N来创建一个新文件,然后选择Data Model来创建一个Core Data模型。这个模型的名字很重要,因为要在代码中引用,我这里命名为"Main"。一旦创建了model,那么就可以在任何想要使用的位置创建实例。

2.2、创建ProgrammingLanguage的实例

打开xcdatamodeld文件并创建一个名为ProgrammingLanguage的实例,我创建了两个字符串属性:"name"和"creator"。你可以根据你的需求创建想要的任何属性了。

2.3、创建PersistenceController单例

需要一个地方来加载和管理Core Data配置,Apple的模版通过一个PersistenceController单例实现。主要做的工作是让Core Data启动并运行,同时也为SwiftUI提供与来Contenxts的能力。

Swift 复制代码
import CoreData

struct PersistenceController {
    //创建单例
    static let shared = PersistenceController()
    //Core Data
    let container: NSPersistentContainer
    //SwiftUI预览配置
    static var preview: PersistenceController = {
        let controller = PersistenceController(inMemory: true)
        
        //创建10个example
        for i in 0..<100 {
            let language = ProgrammingLanguage(context: controller.container.viewContext)
            language.name = "Example Language \(i)"
            language.creator = "A.Programmer \(i)"
        }
        
        return controller
    }()
    //加载Core Data初始化器,将使用内存存储设定为可选值
    init(inMemory: Bool = false) {
        
        //将Main更改为你自己的名
        container = NSPersistentContainer(name: "Main")
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(filePath: "/dev/null")
        }
        container.loadPersistentStores { descroption, error in
            if let error = error {
                fatalError("BBLv Error: \(error.localizedDescription)")
            }
        }
    }
    //向PersistenceController类添加save方法,以便它检查context是否更改。
    func save() {
        let context = container.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print("save error: \(error.localizedDescription)")
            }
        }
    }
}

2.4、将Core Data容器的托管对象context注入到SwiftUI环境中

Swift 复制代码
let persistenceController = PersistenceController.shared

2.5、scenePhase监听App状态

最后一步是可选的,但建议使用,来确保数据完整,针对的场景是当App状态切换为后台时,应该调用save()方法,以便Core Data来保存你的更改,在SwiftUI中,通过添加一个属性到App来监听场景。

Swift 复制代码
@Environment(\.scenePhase) var scenePhase

2.6、结合起来的代码

这段代码写在FFModifierApp中,APP的main入口,由于做了很多其他的例子也用到了这个入口,目前这里代码比较多,这是我整理的只有关于CoreData部分的代码。

Swift 复制代码
@main
struct FFModifierApp: App {
    //将Core Data容器的托管对象context注入到SwiftUI环境中,这里使用perview使用的是假数据,
    //如果真实情况下,使用单例进行初始化(PersistenceController.shared)
    let persistenceController = PersistenceController.preview
    //最后一步是可选的,但建议使用,来确保数据完整,针对的场景是当App状态切换为后台时,
    //应该调用save()方法,以便Core Data来保存你的更改,在SwiftUI中,通过添加一个属性到App来监听场景。
    @Environment(\.scenePhase) var scenePhase

    var body: some Scene {
        WindowGroup {
            FFCoreDataFetchRequest()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
        .onChange(of: scenePhase) {
            persistenceController.save()
        }
    }
}

在上面的PersistenceController.swift类中我选择将数据保存在内存中了,所以该代码的内存存储部分很重要,因为Core Data配置为将信息保存到内存而不是磁盘是,这代表着Core Data中的数据被定义为临时数据,在程序关闭时会被清理。

3、如何从SwiftUI视图访问核心数据管理对象Context

核心代码为@Environment(\.managedObjectContext) var managedObjectContext,在每一个想要使用CoreData的上下文内容上都要使用这个环境变量。下面的例子仅代表着FFCoreDataManagged想要使用数据。但是啥也没干。

Swift 复制代码
struct FFCoreDataManagged: View {
    //将context作为环境变量传递给ContentView(我这里是FFCoreDataManagged),
    //在此视图中添加一个@Environment属性来读取托管对象上下文。
    @Environment(\.managedObjectContext) var managedObjectContext
    var body: some View {
        Text("Hello, World!")
    }
}

4、如何使用@FetchRequest创建一个核心数据获取请求

FetchRequest可以加载符合指定的条件的Core Data结果,并且SwiftUI可以将这些结果直接绑定到用户界面。创建fetchRequest需要两条信息:

  • 要查询的实体
  • 以及确定返回结果顺序的排序描述符。
Swift 复制代码
struct FFCoreDataFetchRequest: View {
    
    @Environment(\.managedObjectContext) var managedObjectContext
    //  SortDescriptors参数是一个数组,所以可以提供尽可能多的排序选项
    @FetchRequest(sortDescriptors: []) var languages: FetchedResults<ProgrammingLanguage>
    
    var body: some View {
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

调试结果

看似简单的代码就成功获得了数据,其实是上面许多步骤后的结果

5、如何使用predicate过滤CoreData的fetchRequests

CoreData的fetchRequest中可以使用Predicates,就像在UIKit中,所有这些都是通向你的@FetchRequest属性包装器提供一个predicate来实现的。

Swift 复制代码
struct FFCoreDataFetchRequestsFilter: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    //创建predicate
    @FetchRequest(
        sortDescriptors: [],
        predicate: NSPredicate(format: "name == %@", "Python")
    ) var languages: FetchedResults<ProgrammingLanguage>
    //目前@FetchRequest使用的是标准的CoreData的Predicate,也可以创建复合Predicate
    
    var body: some View {
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

6、如何从SwiftUI视图添加CoreData对象

在SwiftUI中保存core data对象的工作方式与在SwiftUI之外的工作往事完全相同,访问托管对象的context,在该上下文中创建你的类型的实例,然后在准备好时保存context。

Swift 复制代码
struct FFCoreDataAddObjects: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(sortDescriptors: []) var languages: FetchedResults<ProgrammingLanguage>
    var body: some View {
        //创建实例
        Button("Insert Example Languate") {
            let language = ProgrammingLanguage(context: managedObjectContext)
            language.name = "Python"
            language.creator = "Guido van Rossum"
            PersistenceController.shared.save()
        }
        //在添加一组对象之后,程序会多一组对象。
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

调试结果

7、如何从SwiftUI视图中删除CoreData对象

在SwiftUI中删除CoreData对象与在UIKit中删除他们基本相同,尽管需要跳过一些特殊的步骤才能与SwiftUI视图集成。

Swift 复制代码
struct FFCoreDataDeleteObjects: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(
        sortDescriptors: []
    ) var languages: FetchedResults<ProgrammingLanguage>
    
    var body: some View {
        //无论在哪里显示数据,都向SwiftUI视图添加onDelete修饰符。
        List {
            ForEach(languages) { language in
                Text("Creator: \(language.creator ?? "Anonymous")")
            }
            .onDelete(perform: { indexSet in
                removelanguages(at: indexSet)
            })
        }
        .toolbar(content: {
            EditButton()
        })
    }
    
    func removelanguages(at offsets: IndexSet) {
        for index in offsets {
            let language = languages[index]
            managedObjectContext.delete(language)
        }
        PersistenceController.shared.save()
    }
}

调试结果

8、如何限制获取FetchRequest中的item数量

SwiftUI的@FetchRequest属性包装器非常适合对Object发出简单request,提供排序和过滤功能。但是,如果想调整返回item的数量,那么需要额外设置

Swift 复制代码
struct FFCoreDataLimit: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    //创建@FetchRequest
    @FetchRequest var languages: FetchedResults<ProgrammingLanguage>
    
    init() {
        let request: NSFetchRequest<ProgrammingLanguage> = ProgrammingLanguage.fetchRequest()
        request.sortDescriptors = [
            NSSortDescriptor(keyPath: \ProgrammingLanguage.name, ascending: true)
        ]
        request.fetchLimit = 10
        _languages = FetchRequest(fetchRequest: request)
    }
    
    var body: some View {
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

9、如何在SwiftUI中使用NSUserActivity

SwiftUI有一个专用的onContinueUserActivity()修饰符,可以捕获各种NSUserActivity类型,比如来自网络的点击、来自第三方或者Siri启动等等。在AppDelegate中使用application(_:continue:restorationHandler:)处理过这个问题,但SwiftUI方法更简单的处理这个问题。要实现这个需求,首先要创建一个函数并接受参数NSUserActivity,但是,不需要在App结构体中执行此操作。

Swift 复制代码
func handleWechatLogin(_ userActivity: NSUserActivity) {
    if let id = userActivity.userInfo?["targetContentIdentifier"] {
        print("Found identifier \(id)")
    }
}

使用方法

Swift 复制代码
var body: some Scene {
    WindowGroup {
        FFCoreDataDeleteObjects()
            .environment(\.managedObjectContext, persistenceController.container.viewContext)
            .onContinueUserActivity("targetContentIdentifier", perform: { userActivity in
                handleWechatLogin(userActivity)
            })
    }
    .onChange(of: scenePhase) {
        persistenceController.save()
    }
}
相关推荐
小溪彼岸3 天前
【iOS小组件】小组件尺寸及类型适配
swiftui·swift
文件夹__iOS7 天前
[SwiftUI 开发] @dynamicCallable 与 callAsFunction:将类型实例作为函数调用
ios·swiftui·swift
小溪彼岸8 天前
【iOS小组件】iOS17与低版本兼容适配
swiftui·swift
Mamong9 天前
SwiftUI疑难杂症(1):sheet content多次执行
ios·swiftui·swift
AUV110712 天前
Mac剪贴板历史全记录!
macos·swiftui·mac·效率工具·实用工具·剪贴板·clipboard
AUV110713 天前
Mac 上哪个剪切板增强工具比较好用? 好用剪切板工具推荐
macos·swiftui·mac·剪贴板·clipboard·剪贴板增强·app 推荐
多彩电脑15 天前
SwiftUI里的ForEach使用的注意事项
macos·ios·swiftui·swift
Swift社区16 天前
Apple 新品发布会亮点有哪些 | Swift 周报 issue 61
ios·swiftui·swift
humiaor17 天前
Xcode报错:No exact matches in reference to static method ‘buildExpression‘
swiftui·xcode
humiaor1 个月前
Xcode报错:Return from initializer without initializing all stored properties
swiftui·binding