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

简介: SwiftUI极简教程22:CoreData数据持久化框架的使用(下)

承接上一章的内容,我们继续完成使用CoreData框架搭建一个简单的ToDo待办事项App

这一章节,我们正式进入使用CoreData框架实现数据持久化

image.png

之前我们是通过创建项目的时候勾选使用CoreData框架,系统给我们创建了需要的文件模型。那么这一章节,我们尝试使用CoreData框架达到数据持久化的目的。


Persistence.swift文件


Persistence.swift文件是数据保存到持久存储区的文件,通过将管理对象上下文注入到环境中,来实现在任何视图都可以检索上下文,并且能够管理数据

我们删除多余的示例数据代码。


import Foundation
import CoreData
struct PersistenceController {    
    static let shared = PersistenceController()
    let container: NSPersistentContainer
    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "SwiftUICoreData")
        if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}


image.png

SwiftUICoreDataApp.swift文件


我们回到SwiftUICoreDataApp.swift文件,我们需要将托管对象上下文注入到环境中,这样我们就能够方便地在内容视图中访问上下文,并且管理数据库中的数据。


import SwiftUI
@main
struct SwiftUICoreDataApp: App {
    let persistenceController = PersistenceController.shared
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}


image.png

SwiftUICoreData.xcdatamodeld模型


模型创建完成后,我们需要创建一个新的实体ToDoItem来存储我们需要用到的参数,我们将原来的实体Item重新命名为ToDoItem

ToDoItem实体中,我们需要定义好项目需要的属性

id:UUID
name:String
priorityNum:Integer32
isCompleted:Boolean


由于priority优先级属性是一个Enum枚举类型,为了将Enum枚举类型保存到数据库中,我们必须存储它的原始值,即Int整数,因此需要使用Integer 32类型,而且为了避免命名冲突,我们将属性命名为priorityNum

这里再科普一个知识点。


由于ToDoItem实体我们重新定义了,那么要保证Module模块要选择CurrrentProductModule当前产品的模型,Codegen代码基因要选择Manual/None,不然我们在项目中引用模型的时候可能会找不到我们定义的ToDoItem实体,这非常重要!非常重要!非常重要!重要的事情说三遍。


image.png

CoreData模型类


CoreData框架中,每个实体都与一个模型类配对,我们需要在ToDoItem.swift文件定义模型类。

这里CoreData的模型类继承自NSManagedObject协议,每个属性都使用@NSManaged进行注释,并且对应我们ToDoItem创建的属性id、name、priorityNum、isCompleted


import Foundation
import CoreData
enum Priority: Int {
    case low = 0
    case normal = 1
    case high = 2
}
public class ToDoItem: NSManagedObject {
    @NSManaged public var id: UUID
    @NSManaged public var name: String
    @NSManaged public var priorityNum: Int32
    @NSManaged public var isCompleted: Bool
}
extension ToDoItem: Identifiable {
    var priority: Priority {
        get {
            return Priority(rawValue: Int(priorityNum)) ?? .normal
        }
        set {
            self.priorityNum = Int32(newValue.rawValue)
        }
    }
}

image.png

ContentView.swift文件


定义好CoreData模型类后,我们需要在ContentView主视图中使用它,我们使用@FetchRequest属性包装器从数据库加载数据,我们替换之前使用@State标记的ToDoItem数组数据来源。


@FetchRequest( entity: ToDoItem.entity(),
    sortDescriptors: [ NSSortDescriptor(keyPath: \ToDoItem.priorityNum, ascending: false) ])
var todoItems: FetchedResults<ToDoItem>
复制代码


我们在环境中注入了托管对象上下文,获取所需的数据。

由于我们的List列表数据来源于新定义的todoItems,我们把整个ToDoListViewbody内容拆回到ContentView主视图,这样我们就可以直接遍历循环todoItems的内容。


List {
    ForEach(todoItems) { todoItem in
        ToDoListRow(todoItem: todoItem)
}


image.png

NewToDoView.swift文件


紧接着,我们需要同步更新NewToDoView.swift的代码。要将一个新任务保存到数据库中,首先需要从环境中获取管理对象上下文。


@Environment(\.managedObjectContext) var context

然后,我们在NewToDoView视图和saveButton保存按钮视图就不需要再@Binding绑定ToDoItem数组了。

@Binding var todoItems: [ToDoItem]

然后,我们再更新下addTask添加新事项的方法。

//添加新事项方法
private func addTask(name: String, priority: Priority, isCompleted: Bool = false) {
    let task = ToDoItem(context: context)
    task.id = UUID()
    task.name = name
    task.priority = priority
    task.isCompleted = isCompleted
    do {
        try context.save()
    } catch {
        print(error)
    }
}


image.png

我们要将新事项插入数据库中,需要使用托管上下文创建ToDoItem,然后调用上下文的save函数来提交更改。

因为我们删除了todoItems绑定,所以还需要调整NewToDoView_Previews预览视图。


NewToDoView(name: "", priority: .normal, showNewTask: .constant(true))


由于我们NewToDoView新建事项视图发现变化,我们回到ContentView首页视图,重新修改下绑定关系。


NewToDoView(name: "", priority: .normal, showNewTask: $showNewTask)


同样,我们在ContentView首页视图也需要从环境中获取管理对象上下文。


@Environment(\.managedObjectContext) var context


ToDoListRow视图


与添加新事项类似,我们需要获取用于记录更新的托管对象上下文。对于Toggle优先级选择视图,每当切换发生更改时,todoItemisCompleted属性将被更新。我们可以附加onReceive修饰符,onReceive修饰符可以监听isCompleted属性和其他我们定义好的属性的更改,并通过调用上下文的save函数将它们保存到持久存储区。


//监听todoItem数组参数变化并保存
.onReceive(todoItem.objectWillChange, perform: { _ in
    if self.context.hasChanges {
        try? self.context.save()
    }
})


image.png

deleteTask删除事项方法


上面我们既然完成了addTask新增事项方法,那么顺便完成deleteTask删除事项的交互。

//删除事项方法
private func deleteTask(indexSet: IndexSet) {
    for index in indexSet {
        let itemToDelete = todoItems[index]
        context.delete(itemToDelete)
    }
    DispatchQueue.main.async {
        do {
            try context.save()
        } catch {
            print(error)
        }
    }
}


deleteTask删除事项方法接收一个存储要删除的项的索引集,我们只需要调用上下文的delete函数,并指定要删除的项,然后调用save函数来提交更新。

我们把这个方法加到List里,就可以实现滑动删除的操作。


List {
    ForEach(todoItems) { todoItem in
        ToDoListRow(todoItem: todoItem)
    }.onDelete(perform: deleteTask)
}


image.png

preview预览效果


我们运行模拟器的时候发现报错了,这是因为我们没有在contentview_preview结构体中注入托管对象上下文。我们需要创建一个内存中的数据存储,并准备一些测试数据。

我们在Persistence.swift文件创建了一个PersistenceController实例,并将inMemory参数设置为true,然后我们添加10个示例待办事项,并将它们保存到数据存储中。


import CoreData
struct PersistenceController {
    static let shared = PersistenceController()
    let container: NSPersistentContainer
    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for index in 0..<10 {
            let newItem = ToDoItem(context: viewContext)
            newItem.id = UUID()
            newItem.name = "待办事项\(index)"
            newItem.priority = .normal
            newItem.isCompleted = false
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()
    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "SwiftUICoreData")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}


之后我们如果只需要在ContentView_Previews预览视图的上下文注入到内容视图的环境中,就可以看到示例的数据啦~


struct ContentView_Previews: PreviewProvider {
    static var previews: some View { 
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}


image.png

快来动手试试吧!

相关文章
|
2月前
|
iOS开发 开发者 MacOS
深入探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】 本文将带领读者深入了解Apple最新推出的SwiftUI框架,这一革命性的用户界面构建工具为iOS开发者提供了一种声明式、高效且直观的方式来创建复杂的用户界面。通过分析SwiftUI的核心概念、主要特性以及在实际项目中的应用示例,我们将展示如何利用SwiftUI简化UI代码,提高开发效率,并保持应用程序的高性能和响应性。无论你是iOS开发的新手还是有经验的开发者,本文都将为你提供宝贵的见解和实用的指导。
143 66
|
3月前
|
iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第39天】在苹果的生态系统中,SwiftUI框架以其声明式语法和易用性成为开发者的新宠。本文将深入SwiftUI的核心概念,通过实际案例展示如何利用这一框架快速构建用户界面,并探讨其对iOS应用开发流程的影响。
|
5月前
|
iOS开发 开发者 UED
探索iOS应用开发中的SwiftUI框架
【9月更文挑战第26天】 在iOS开发的海洋中,SwiftUI犹如一艘现代的快艇,引领着开发者们驶向更加高效与直观的编程体验。本文将带你领略SwiftUI的魅力,从其设计理念到实际应用,我们将一步步揭开它如何简化界面构建过程的面纱。通过对比传统方式,你将看到SwiftUI如何让代码变得像诗一样优美,同时保持强大的功能性和灵活性。准备好让你的iOS开发技能加速升级,一起驾驭这股新潮流吧!
|
7月前
|
Swift iOS开发 开发者
iOS应用开发:SwiftUI高级技巧
【7月更文挑战第21天】SwiftUI以其声明式语法、跨平台一致性、强大的动画和状态管理特性,为iOS应用开发带来了革命性的变化。通过掌握SwiftUI的高级技巧,开发者可以构建出更加高效、美观和流畅的应用。希望本文能够帮助你更好地理解和应用SwiftUI,从而在iOS应用开发的道路上走得更远。
|
9月前
|
开发框架 前端开发 Swift
SwiftUI的优缺点
SwiftUI的优缺点
308 0
|
9月前
|
编译器 API Swift
【Swift开发专栏】Swift中的SwiftUI框架初探
【4月更文挑战第30天】SwiftUI是苹果2019年推出的界面构建框架,简化iOS应用开发。通过声明式语法和编译器优化,提供直观高效的UI设计。本文将介绍SwiftUI概述、主要特性及实际案例。SwiftUI强调“少即是多”,用少量代码实现复杂界面,提供简洁API、自动布局、双向数据绑定等功能。通过视图组合和实时预览加速开发。案例展示如何用SwiftUI构建用户列表界面,体现其结构清晰、易扩展的优势。SwiftUI在iOS开发中的重要性日益提升。
126 0
|
9月前
|
API 数据库 Swift
【Swift开发专栏】Swift中的数据持久化:Core Data与Realm
【4月更文挑战第30天】本文探讨了Swift中两种流行的数据持久化框架——Core Data和Realm。数据持久化是保持应用数据在不同运行周期间一致性的关键。Core Data,苹果的ORM系统,适合处理复杂数据关系,提供与iOS生态系统的无缝集成。使用Core Data涉及定义数据模型、生成NSManagedObject子类、配置持久化容器及执行数据操作。而 Realm,一个轻量级数据库,以其高性能、易于使用的API和实时数据同步适用于跨平台项目。在Swift中使用Realm,需定义数据模型、配置Realm实例、执行数据操作并观察数据变化。理解这两者能帮助开发者构建更高效、可靠的应用。
234 0
|
9月前
|
SQL 存储 开发框架
EntityFramework数据持久化复习资料6、EntityFramework引入
EntityFramework数据持久化复习资料6、EntityFramework引入
61 0
|
9月前
|
存储 数据库 iOS开发
IOS开发数据存储:什么是 CoreData?如何在应用中使用它?
IOS开发数据存储:什么是 CoreData?如何在应用中使用它?
268 0
|
存储 数据库 Swift
SwiftUI极简教程20:CoreData数据持久化框架的使用(上)
SwiftUI极简教程20:CoreData数据持久化框架的使用(上)
1394 1
SwiftUI极简教程20:CoreData数据持久化框架的使用(上)