一同养成写作习惯!这是我参与「日新方案 4 月更文应战」的第20天,点击查看活动概况。

今天职言:深化思考和坚持平等重要。

在本章中,你将学会运用CoreData数据耐久化结构建立一个简单的ToDo待办事项App

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

咱们在之前的学习中构建过List列表和SwipeCard卡片,咱们发现假如咱们重新启动模拟器,它的数据会恢复原始数组的数据。这是由于每次翻开App的时分,体系会根据数据源重新遍历数据,当用户关闭应用程序并重新启动时,一切数据都“消失”了。

那么本章咱们学习一个新的结构,叫做CoreData,它一个办理数据目标的结构,能够将咱们的数据保存起来,这样每当咱们重新翻开App的时分,App展现的就是咱们上一次操作的数据。

值得注意的一点是,CoreData可不是数据库哦,它仅仅一个用于开发人员办理和存储数据耐久化的交互结构,它的耐久存储并不局限于数据库。

好了,说了那么多,让咱们正式开始吧。

首要,创立一个新项目,命名为SwiftUICoreData,请注意,这里咱们需求勾选运用CoreData

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

CoreData结构数据耐久化完结原理

咱们发现,和以往创立的App不同,这次多了几个文件。

一个是SwiftUICoreData.xcdatamodeld文件,它是办理整个项目生成的目标模型的,是界说实体与耐久存储交互的文件。

另一个是Persistence.swift文件,它是数据保存到耐久存储区的文件。

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

SwiftUI经过将办理目标上下文viewContext注入到环境中,来完结在任何视图都能够检索上下文,并且能够办理数据

咱们再看一下SwiftUICoreDataApp.swift文件,能够看到它界说了一个常量persistenceController来保存PersistenceController的实例,并在ContentView主视图中将保管目标上下文viewContext注入到环境中。

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

上面咱们看到已经在办理目标模型中创立实体了,并且界说一个承继自NSManagedObject的办理目标来与实体相关。

咱们回到ContentView.swift文件,能够看到体系生成了一堆的示例代码,让咱们解读一下。

首要运用了@Environment环境变量从环境中获取保管目标上下文viewContext

@Environment(\.managedObjectContext) var context

然后创立办理目标,并运用context上下文的save办法将目标增加到数据库中:

//示例代码
let task = ToDoItem(context: context)
task.id = UUID()
task.name = name
task.priority = priority
task.isCompleted = isCompleted

数据检索方面,咱们引入了一个名为@FetchRequest的属性包装器,用于从耐久存储中获取数据。它能够指定要检索的实体目标以及数据的排序办法,然后,CoreData结构就能够将运用@Environment环境的保管目标上下文context来获取数据。

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

好了,以上就是CoreData结构数据耐久化完结的原理,咱们能够预览下体系提供的比方。

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

下面,让咱们进入正题。

ToDoItem类准备

首要,咱们需求界说一个模型类,咱们能够创立一个新的文件,点击Xcode顶部导航栏,File文件,New新建,挑选File创立文件,挑选iOS中的Swift File类型的文件,命名为ToDoItem.swift

然后咱们构建需求App需求的参数。

咱们先构建一个枚举类型Priority,来表明咱们使命的优先级,分别是低、中、高、最高,用数值Int类型表明权重。

//使命紧急程度的枚举
enum Priority: Int {
case low = 0
case normal = 1
case high = 2
}

然后界说一个类ToDoItem遵从ObservableObject可被观察目标协议和Identifiable可被辨认协议,在ToDoItem类里边有三个参数:name名称、priority优先级、isCompleted是否完结。并且在ObservableObject协议需求运用@Published界说,这样才能在参数改变的时分检测到变化

至于遵从Identifiable协议就不用说了,咱们界说id作为每一个使命项的仅有标识符,这样即便是相同名称、相同优先级的使命,体系也不会把它们作为同一个,这个咱们之前的章节讲过。

//ToDoItem遵从ObservableObject协议
class ToDoItem: ObservableObject, Identifiable {
var id = UUID()
@Published var name: String = ""
@Published var priority: Priority = .high
@Published var isCompleted: Bool = false
//实例化
init(name: String, priority: Priority = .normal, isCompleted: Bool = false) {
self.name = name
self.priority = priority
self.isCompleted = isCompleted
}
}

咱们回到ContentView.swift文件,咱们看看需求做哪些东西。

TopBarMenu顶部导航栏

首要是TopBarMenu顶部导航栏,比较简单,在这里就不赘述了。

//顶部导航栏
struct TopBarMenu: View {
var body: some View {
HStack {
Text("待办事项")
.font(.system(size: 40, weight: .black))
Spacer()
Button(action: {
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle).foregroundColor(.blue)
}
}
.padding()
}
}

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

中间的内容部分,咱们能够看到有两种状况,一种是没有数据的时分,咱们展现一张Image图片,另一种是有数据的时分,展现List数据列表。

NoDataView缺省页

咱们导入一张图片,命名叫做image01,然后构建第一种空数据的状况,业务上常常叫做缺省页的图。

//缺省图
struct NoDataView: View {
var body: some View {
Image("image01")
.resizable()
.scaledToFit()
}
}

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

假如List列表有数据的时分,咱们需求展现列表数据,接下来,咱们完结下List的创立。

ToDoListView列表页创立

之前的章节咱们了解过List列表的创立办法,这里咱们先构建单个使命项ToDoListRow视图的款式,然后运用List列表+ForrEach循环的办法构建整个列表ToDoListView

// 列表
struct ToDoListView: View {
@Binding var todoItems: [ToDoItem]
var body: some View {
List {
ForEach(todoItems) { todoItem in
ToDoListRow(todoItem: todoItem)
}
}
}
}
// 列表内容
struct ToDoListRow: View {
@ObservedObject var todoItem: ToDoItem
var body: some View {
Toggle(isOn: self.$todoItem.isCompleted) {
HStack {
Text(self.todoItem.name)
.strikethrough(self.todoItem.isCompleted, color: .black)
.bold()
.animation(.default)
Spacer()
Circle()
.frame(width: 20, height: 20)
}
}
}
}

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

咱们在ToDoListView列表视图运用@Binding(图中有误)声明晰一个todoItems状况,用来存储ToDoItem数组,当数据变化时就刷新页面。

//ContentView视图
VStack {
    TopBarMenu()
    ToDoListView(todoItems: $todoItems)
}

然后咱们在ToDoListRow视图运用@ObservableObject声明晰一个todoItem,用来引用界说好的实例化办法。

关于ToDoListRow单个使命项的视图,里边也比较简答,咱们用了一个Toggle开关作为复选框,再加上一个Text文字作为待办事项的内容标题,最后咱们还用了一个Circle圆形的形状,作为priority标识。

priority标识咱们能够界说一个私有的色彩办法,当咱们从Priority枚举类型中获得不同状况时,返回不同的色彩,比方优先级高显现红色一般优先级显现橘色低优先级显现绿色

// 根据优先级显现不同色彩
private func color(for priority: Priority) -> Color {
 switch priority {
case .high:
return .red
case .normal:
return .orange
case .low:
return .green
}
}

界说好办法后,咱们将Circle圆形赋予布景色彩,色彩值调用priority界说色彩办法。

.foregroundColor(self.color(for: self.todoItem.priority))

然后关于Toggle开关,咱们期望用的是checkbox复选框的款式,还记得之前的章节中咱们用ButtonStyle修改Button按钮的款式么?

是的,Toggle开关也支持自界说款式的办法,咱们能够用ToggleStyle开关款式把Toggle开关变成checkbox复选框。

// checkbox复选框款式
struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(configuration.isOn ? .purple : .gray)
.font(.system(size: 20, weight: .bold, design: .default))
.onTapGesture {
configuration.isOn.toggle()
}
configuration.label
}
}
}

然后,咱们给Toggle开关增加.toggleStyle开关款式修饰符就能够将自界说好的款式加到里边了。

.toggleStyle(CheckboxStyle())

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

以上,咱们完结了空的列表NoDataView缺省页,还有有数据时的列表ToDoListView待办事项列表,当然现在ToDoListView待办事项列表还没有数据,别急,咱们慢慢来。

页面展现逻辑判别

那么什么时分展现NoDataView缺省页视图,什么时分展现ToDoListView待办事项列表视图呢?

当然是todoItems没有数据的时分展现NoDataView缺省页视图,todoItems有数据的时分展现ToDoListView待办事项列表视图。

咱们就能够把这个判别加到ContentView主视图里边。

if todoItems.count == 0 {
 NoDataView()
}

最后,在ContentView主视图布局部分,咱们将TopBarMenu顶部导航栏、ToDoListView待办事项列表用VStack笔直排布在一同,然后运用ZStack层叠视图将NoDataView缺省页视图包裹在一同看看作用。

//主视图
struct ContentView: View {
@State var todoItems: [ToDoItem] = []
var body: some View {
ZStack {
VStack {
TopBarMenu()
ToDoListView()
}
if todoItems.count == 0 {
NoDataView()
}
}
}
}

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

嗯?为啥List列表会有布景色彩?这是iOS14的新特性,假如咱们需求去掉这个色彩,需求再做一下处理,在视图加载的时分,将TableView列表和TableViewCell列表项的布景色彩变成无填充颜.clear

//去掉Listb布景色彩
init() {
 UITableView.appearance().backgroundColor = .clear
 UITableViewCell.appearance().backgroundColor = .clear
}

这样,咱们就完结了列表展现页的制造。

SwiftUI极简教程20:CoreData数据持久化框架的使用(上)

由于章节篇幅太长,将分为上下两章来写,上半部分先完结主要页面的构建,下半部分咱们再完结NewToDoView新增使命项页面和根据CoreData结构数据耐久化的逻辑部分。

本章完好代码如下:

//ToDoItem.swift
import Foundation
enum Priority: Int {
case low = 0
case normal = 1
case high = 2
}
class ToDoItem: ObservableObject, Identifiable {
var id = UUID()
@Published var name: String = ""
@Published var priority: Priority = .high
@Published var isCompleted: Bool = false
init(name: String, priority: Priority = .normal, isCompleted: Bool = false) {
self.name = name
self.priority = priority
self.isCompleted = isCompleted
}
}
//ContentView.swift
import CoreData
import SwiftUI
struct ContentView: View {
@State var todoItems: [ToDoItem] = []
//去掉Listb布景色彩
init() {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
}
var body: some View {
ZStack {
VStack {
TopBarMenu()
ToDoListView(todoItems: $todoItems)
}
if todoItems.count == 0 {
NoDataView()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// 顶部导航栏
struct TopBarMenu: View {
var body: some View {
HStack {
Text("待办事项")
.font(.system(size: 40, weight: .black))
Spacer()
Button(action: {
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle).foregroundColor(.blue)
}
}
.padding()
}
}
// 缺省图
struct NoDataView: View {
var body: some View {
Image("image01")
.resizable()
.scaledToFit()
}
}
// 列表
struct ToDoListView: View {
@Binding **var** showNewTask: Bool
var body: some View {
List {
ForEach(todoItems) { todoItem in
ToDoListRow(todoItem: todoItem)
}
}
}
}
// 列表内容
struct ToDoListRow: View {
@ObservedObject var todoItem: ToDoItem
var body: some View {
Toggle(isOn: self.$todoItem.isCompleted) {
HStack {
Text(self.todoItem.name)
.strikethrough(self.todoItem.isCompleted, color: .black)
.bold()
.animation(.default)
Spacer()
Circle()
.frame(width: 20, height: 20)
.foregroundColor(self.color(for: self.todoItem.priority))
}
}.toggleStyle(CheckboxStyle())
}
// 根据优先级显现不同色彩
private func color(for priority: Priority) -> Color {
switch priority {
case .high:
return .red
case .normal:
return .orange
case .low:
return .green
}
}
}
// checkbox复选框款式
struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(configuration.isOn ? .purple : .gray)
.font(.system(size: 20, weight: .bold, design: .default))
.onTapGesture {
configuration.isOn.toggle()
}
configuration.label
}
}
}

快来着手试试吧!

假如本专栏对你有协助,无妨点赞、评论、关注~