专栏首页韦弦的偶尔分享如何结合 Core Data 和 SwiftUI

如何结合 Core Data 和 SwiftUI

core data stack

SwiftUI 和 Core Data 之间相差将近十年——SwiftUI 随着 iOS 13面世而 Core Data 则是iPhoneOS 3的产物;很久以前,它还没有被称为iOS,因为iPad尚未发布。尽管时间相距遥远,Apple还是投入了大量工作以确保这两种强大的技术能够完美地相互配合使用,这意味着Core Data就像始终以这种方式设计一样,已集成到SwiftUI中。

在此项目中,我们将仅使用少量Core Data的功能,但是这种功能将很快扩展——我只想首先了解一下它。当您创建Xcode项目时,我要求您选中 Use Core Data 框,它应该导致对项目的更改:

  • 现在,您有了一个名为 Bookworm.xcdatamodeld 的文件。这描述了您的数据模型,该数据模型实际上是类及其属性的列表。
  • AppDelegate.swift 和 SceneDelegate.swift 中现在有用于设置 Core Data 的额外代码。

设置核心数据需要两个步骤:创建所谓的持久性容器(从容器存储中加载并保存实际数据),然后将其注入SwiftUI 环境中,以便我们所有的视图都可以访问它。

Xcode模板已经为我们完成了这两个步骤。

因此,剩下的就是我们要决定要在Core Data中存储哪些数据,以及如何读出这些数据。首先,我们需要打开 Bookworm.xcdatamodeld 并开始使用 Xcode 的模型编辑器描述我们的数据。

之前我们描述过这样的数据:

struct Student {
    var id: UUID
    var name: String
}

但是,Core Data 不能那样工作。您会看到,Core Data 需要提前知道我们所有数据类型的样子,包含的内容以及它们之间的关系。这就是“xcdatamodeld”文件的来源:我们将类型定义为“实体”,然后在其中创建属性作为“属性”,Core Data 负责将其转换为可以在运行时使用的实际数据库布局。

为了进行试用,请点击 “Add Entity” 按钮创建一个新实体,然后双击其名称将其重命名为“ Student”。接下来,单击“Attributes”表正下方的+按钮以添加两个属性:“ id”作为UUID和“name”作为字符串。这将告诉Core Data创建学生并保存他们所需的一切,因此请回到ContentView.swift,以便我们编写一些代码。

使用获取请求从Core Data中检索信息——我们描述了我们想要的内容,应如何对其进行排序以及是否应使用任何过滤器,然后Core Data会发回所有匹配的数据。我们需要确保该获取请求随着时间的推移保持最新,以便在创建或删除学生时,我们的UI保持同步。

SwiftUI有一个解决方案,而且——您猜对了——这是另一个属性包装器。这次将其称为@FetchRequest,它带有两个参数:我们要查询的实体以及我们希望结果如何排序。它具有非常特定的格式,因此,我们首先为学生添加获取请求——请立即将此属性添加到 ContentView

@FetchRequest(entity: Student.entity(), sortDescriptors: []) var students: FetchedResults<Student>

分解之后,这创建了一个获取的“学生”实体的请求,不进行任何排序,而是将其放入名称为students,类型为FetchedResults<Student>的属性中。

从那里开始,我们可以像常规的Swift数组一样开始使用学生,但是您会发现有一个陷阱。首先,一些将数组放入List的代码:

    var body: some View {
        VStack {
            List {
                ForEach(students, id: \.id) { student in
                    Text(student.name ?? "Unknown")
                }
            }
        }
    }
}

你发现异常了吗?是的,student.name是可选的——它可能有一个值,也可能没有。这是Core Data的一个领域,该领域会让您大为恼火:它具有可选数据的概念,但与Swift的可选数据完全不同。如果我们对Core Data说“这不是必须的”(您可以在模型编辑器中完成),它仍然会生成可选的Swift属性,因为所有Core Data关心的是属性在保存时具有值——在其他时间它们可以为 nil。

您可以根据需要运行代码,但没有太多意义——该列表将为空,因为我们尚未添加任何数据,因此我们的数据库为空。为了解决这个问题,我们将在列表下方创建一个按钮,每次点击都会添加一个新的随机学生,但是首先我们需要一个新属性来存储托管对象上下文。

让我重申一下,因为这很重要。当我们定义“Student”实体时,实际上发生的是 Core Data 为我们创建了一个类,该类继承自其自身的一个类:NSManagedObject。我们无法在代码中看到该类,因为它是在构建项目时自动生成的,就像 Core ML 的模型一样。这些对象之所以称为托管对象,是因为 Core Data 会照料它们:它从持久性容器中加载它们并将它们的更改也写回。

我们所有的托管对象都位于托管对象上下文中,该上下文负责实际获取托管对象以及保存更改等。如果需要的话,您可以有许多托管对象上下文,但这距离现在还有一段路要走——实际上,您可以长期使用它。

我们不需要创建此托管对象上下文,因为Xcode已经为我们创建了一个。更好的是,它已经将其添加到SwiftUI环境中,这就是@FetchRequest属性包装器起作用的原因——它使用了环境中可用的任何托管对象上下文。

因此,现在将此属性添加到ContentView

@Environment(\.managedObjectContext) var moc

设置好之后,下一步是添加一个按钮,该按钮生成随机的学生并将其保存在托管对象上下文中。为了帮助学生脱颖而​​出,我们将通过创建firstNameslastNames数组来分配随机名称,然后使用randomElement()从中选择一个。

首先在List下方添加此按钮:

Button("Add") {
    let firstNames = ["Ginny", "Harry", "Hermione", "Luna", "Ron"]
    let lastNames = ["Granger", "Lovegood", "Potter", "Weasley"]

    let chosenFirstName = firstNames.randomElement()!
    let chosenLastName = lastNames.randomElement()!

    // more code to come        
}

注意:不可避免地有人会抱怨我强行对randomElement()调用,但是实际上我们只是手工创建了具有值的数组——它将永远成功。如果您非常讨厌强制拆包,则可以将其替换为空合计算和默认值。

现在,有趣的部分是:我们将使用为我们生成的 Core Data 类创建一个 Student对象。这需要附加到托管对象上下文中,以便对象知道应将其存储在何处。然后,我们可以像通常为结构体那样分配值。

因此,现在将这三行添加到按钮的操作闭包中:

let student = Student(context: self.moc)
student.id = UUID()
student.name = "\(chosenFirstName) \(chosenLastName)"

最后,我们需要询问托管对象上下文以保存自身。这是一个引发函数的调用,因为理论上它可能会失败。实际上,我们所做的一切都没有失败的可能,因此我们可以使用try?来调用它——–我们不在乎捕获错误。

因此,请将最后一行添加到按钮的操作中:

try? self.moc.save()

最后,您现在应该可以运行该应用程序并对其进行尝试——单击几次“Add”按钮以生成一些随机的学生,您应该看到他们滑入我们列表的某个位置。更好的是,如果您重新启动该应用程序,您会发现学生还在,因为Core Data已保存了他们。

现在,您可能认为这需要大量的学习,但并不会带来很多结果,但是您现在知道什么是实体和属性,知道什么是托管对象和请求,并且已经了解了如何保存更改。在此项目的后面以及将来,我们都将更多地关注Core Data,但到目前为止,您已经走了很远。

这是该项目概述的最后一部分,因此,请将您的代码重设为初始状态,并确保您从我们的数据模型中删除了Student实体——我们不再需要它。

PS: 如果预览报错,那么请跑模拟器

译自 How to combine Core Data and SwiftUI

使用 size classes 时通过 AnyView 实现类型擦除

Hacking with iOS: SwiftUI Edition

书虫项目(一)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 100 Days of SwiftUI —— Day 55:书虫(三)

    今天完成我们的应用程序后,希望您能够停止并意识到自己已经知道多少SwiftUI。例如,要为我们的应用程序构建详细信息屏幕,将利用 Core Data,Geome...

    韦弦zhy
  • 100 Days of SwiftUI —— Day 54:书虫(二)

    今天,我们将开始应用您学到的新技术来构建我们的应用程序,使用Core Data来创建图书,并使用通过@Binding构建的自定义的RatingView组件来让用...

    韦弦zhy
  • 100 Days of SwiftUI —— Day 65:Instafilter(四)

    今天,我们将实践一些您刚刚学到的技术,包括如何创建自定义绑定,如何包装UIImagePickerController以在SwiftUI中使用以及如何将Core ...

    韦弦zhy
  • select 信道好帮手

    select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select 会...

    酷走天涯
  • .NET Core 3和对Windows桌面应用程序的支持

    在今天的微软Build Live大会上,微软.Net Core团队公开了.net Core3的开发计划的预览。.Net Core 3 的亮点是支持Windows...

    程序你好
  • 一个很好玩的 GitHub Repo, 用核心价值观编码和反编码信息

    今天在网上发现一个很好玩的GitHub Repo,可以将你想要说的话编码成为类似富强民主文明和谐这样的格式,是不是再也不怕被删帖啦哈哈,不过可不要作死哦!文末给...

    浩Coding
  • Go 语言 Web 编程系列(一)—— 快速入门:创建第一个 Web 应用

    首先,我们基于 HTTP 编程中介绍的 net/http 包来实现一个简单的 HTTP 服务器作为 Web 服务器:

    学院君
  • 某搜索引擎Self-XSS点击劫持案例分享

    在AI横飞的今天,网站页面不挂个聊天机器人都会觉得low,笔者在某搜索引擎的页面上就发现了这样一个聊天AI,无聊一试发现了一个xss。

    FB客服
  • 深度学习-矩阵求导的坑

    石晓文
  • 聊聊kafka的group coordinator

    本文主要来讲一个kafka的group coordinator。在kafka0.9.0版本的时候,开始启用了新的consumer config,这个新的cons...

    codecraft

扫码关注云+社区

领取腾讯云代金券