置顶
菜鸟入门笔记,如有谬误之处还请大佬指出
深耕细作 笃行致远
相关文章
- 《SwiftUI入门 - Core Data初探与实践》
- 《SwiftUI入门 - Core Data包装器@FetchRequest的排序、筛选和分页》
- 《SwiftUI入门 - Core Data数据的分组、聚合和汇总》
- 《SwiftUI入门 - Core Data实体的关联关系(一对一、一对多、多对多)》
如需获取其他SwiftUI知识点,请移步至SwiftUI入门专栏
前言
在上一篇文章《SwiftUI入门 - Core Data初探与实践》中,我们已知晓 Core Data
模型、实体和实体属性的创建,并通过编写 CRUD
函数完成了实体数据的增删改查操作。在实现查询数据的过程中对 @FetchRequest
属性包装器的使用则是一笔带过,并未对其进行详细的了解。本文中将对Core Data
的排序、筛选和分页的使用方法进行学习和探究。
物料
创建测试实体
在下方的截图中,我们创建了 User
实体,并为其添加三个属性:
- name(用户名称):
String
类型 - score(积分):
Integer 32
类型,实际应用中积分使用Integer 16
已经足够,这里仅用于测试验证无需过于教条 - timestamp(注册时间):
Date
类型
创建测试数据
// 定义测试序列化数据的结构体
struct UserItem{
var name:String;
var score:Int32;
var timestamp: Date;
}
// 初始化测试数据方法
func initTestData(){
let list: [UserItem] = [
UserItem(name: "张三", score: 60, timestamp: Date()),
UserItem(name: "李四", score: 70, timestamp: Date().addingTimeInterval(60)),
UserItem(name: "王五", score: 10, timestamp: Date().addingTimeInterval(3600)),
UserItem(name: "赵六", score: 20, timestamp: Date().addingTimeInterval(120)),
UserItem(name: "刘七", score: 70, timestamp: Date().addingTimeInterval(3600)),
UserItem(name: "周一", score: 40, timestamp: Date().addingTimeInterval(3600)),
UserItem(name: "将二", score: 50, timestamp: Date().addingTimeInterval(5960)),
UserItem(name: "王五", score: 100, timestamp: Date().addingTimeInterval(1230)),
UserItem(name: "张三", score: 70, timestamp: Date().addingTimeInterval(567)),
UserItem(name: "建安", score: 90, timestamp: Date().addingTimeInterval(345)),
];
// 遍历存入缓存
for element in list{
let user = User(context: viewContext)
user.name = element.name
user.score = element.score
user.timestamp = element.timestamp
}
do {
// 保存数据入库
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
在上方的代码中,我们定义了一个结构体将测试数据存储到数组中,以便于循环入库
粘贴上方代码到你的应用中,执行 initTestData()
函数即可,如若 viewContext
与你的名称不同,请稍作调整
排序
在常见应用使用场景中,我们通常需要对数字或字符串字段按字段的先后顺序和字段值的升序/降序进行排序,Core Data
包装器@FetchRequest
同样提供了这种能力
@FetchRequest(
// 排序描述
sortDescriptors: [
// 排序规则
NSSortDescriptor(keyPath: \User.timestamp, ascending: true),
NSSortDescriptor(keyPath: \User.score, ascending: false)
]
)
var users: FetchedResults<User>
var body: some View{
VStack{
ForEach(users){item in
HStack{
Text(item.name ?? "")
Spacer()
Text("\(item.score)")
Spacer()
Text(formatDate(date: item.timestamp!))
}.padding()
}
Spacer()
Button {
initTestData()
} label: {
Text("初始化测试数据")
}
}
}
// 日期格式化函数
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter.string(from: date)
}
- 我们使用
sortDescriptors
对排序进行描述,sortDescriptors
接受一个数组,允许对多个字段(属性)进行排序 - 使用
NSSortDescriptor
结构体添加排序规则,keyPath
为实体的属性名,ascending
则决定升序/降序
排序后展示结果如下:
筛选
在SwiftUI中,@FetchRequest
的 predicate
参数用于过滤 Core Data
中的数据。通过使用 predicate
可以指定特定的条件,只获取符合条件的数据,从而对数据进行筛选和显示。
@FetchRequest(
predicate:NSPredicate(format: "name like %@", argumentArray: ["张三"])
)
var users: FetchedResults<User>
format: 等于是SQL中 where 语句,支持包括[or, >, <, =, like]
等SQL中所有的操作符,%@
作为占位符,最终会被argumentArray
数组中的参数依次替换
查询结果如下:
分页
在研究分页时发现@FetchRequest
并没有直接提供分页的能力,它会一次性获取所有满足条件的数据放入内存,那么在数据过大的情况下必然会出现页面卡顿或内存溢出的问题。鉴于这种情况,我们采用手动组装请求来获取数据。
由于此部分代码较多,代码执行逻辑写到代码注释中
import SwiftUI
import CoreData
struct CoreDataView: View {
// 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
@Environment(\.managedObjectContext) var viewContext
// 设置每页数据项数
let pageSize = 3
// 当前页数
@State private var currentPage = 1
// 总页数
@State private var totalPage = 1
var body: some View{
// 懒加载布局
LazyVStack{
// 循环数据从 fetchUsers() 方法中获取
ForEach(fetchUsers(), id: \.self){item in
HStack{
Text(item.name ?? "")
Spacer()
Text("\(item.score)")
Spacer()
// formatDate() 方法格式化日期时间
Text(formatDate(date: item.timestamp!))
}.frame(height: 50)
}
}.onAppear {
// 获取总页数
fetchTotalPage()
// 初始化加载第一页数据
fetchNextPage()
}.padding()
Spacer()
HStack{
Button {
fetchPrePage()
} label: {
Text("上一页")
}
Spacer()
Button {
fetchNextPage()
} label: {
Text("下一页")
}
}.padding()
}
// 获取数据列表
private func fetchUsers() -> [User] {
// 通过 User 类中内置的 fetchRequest() 生成一个请求对象
let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
// 添加排序描述
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \User.timestamp, ascending: true)]
// 设置分页条数
fetchRequest.fetchLimit = pageSize
// 设置偏移量(即从第几条数据开始获取)
fetchRequest.fetchOffset = (currentPage - 1) * pageSize
do {
// 通过 viewContext.fetch() 直接获取数据
let fetchedItems = try viewContext.fetch(fetchRequest)
return fetchedItems
} catch {
print("Error fetching items: \(error)")
return []
}
}
// 获取总页数
private func fetchTotalPage() {
// 生成 User 请求对象
let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
do {
// 获取总条数
let totalCount = try viewContext.count(for: fetchRequest)
totalPage = Int(ceil(Double(totalCount) / Double(pageSize)))
} catch {
print("Error fetching total page count: \(error)")
totalPage = 1
}
}
// 上一页函数
func fetchPrePage(){
if(currentPage>1){
currentPage -= 1
}
}
// 下一页函数
func fetchNextPage() {
// 计算下一页页数
if(currentPage < totalPage){
currentPage += 1
}
}
// 日期格式化函数
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter.string(from: date)
}
}
总结
- 我们通过
@FetchRequest
的形参sortDescriptors
与NSSortDescriptor
结构体来对查询数据进行排序 - 使用
predicate
进行数据筛选过滤,了解到NSPredicate
中的format
其实就是SQL
中的where
语句 - 在研究分页时发现 @FetchRequest 并不直接支持分页,需要通过
NSFetchRequest
的实例来组装请求 - 还学习到了 Swift 中 DateFormatter 日期格式化类的使用
别错过精彩内容,快来关注我们的微信公众号【寻桃】!