SwiftUI入门 - Core Data数据的分组、聚合和汇总

950 阅读4分钟

置顶

菜鸟入门笔记,如有谬误之处还请大佬指出

深耕细作 笃行致远

相关文章

如需获取其他SwiftUI知识点,请移步至SwiftUI入门专栏

前言

在上一章笔记《SwiftUI入门 - Core Data包装器@FetchRequest的排序、筛选和分页》中,我们知晓了数据的排序、查询和分页等相关知识点。在本章节中我们将学习Core Data数据分组、聚合和汇总的使用,也是对上一章节内容的补充。

物料

直接沿用上一章的实体和测试数据,如若还不清楚实体的创建与测试数据的导入,请移步至上一章,这里不详细阐述此过程。

创建测试实体

image.png

创建测试数据

    // 定义测试序列化数据的结构体
    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)")
        }
    }

分组

分组的作用不言而喻,在诸多的业务场景中,例如中台、大屏和数据分析等场景均在使用,是对某个特定事物的分组,以达到归类的目的

    import SwiftUI
    import CoreData

    struct GroupView: View {
        // 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
        @Environment(\.managedObjectContext) var viewContext
        // 分组包装器
        @SectionedFetchRequest<Int32, User>(
            sectionIdentifier: \.score,
            sortDescriptors: [
                NSSortDescriptor(keyPath: \User.score, ascending: false)
            ]
        )
        var users: SectionedFetchResults<Int32, User>

        var body: some View{
            // 滚动视图
            ScrollView{
                VStack{
                    // 循环分组用户列表
                    ForEach(users){section in
                        HStack{
                            // 获取当前分组值
                            Text("\(section.id)").padding(8)
                            Spacer()
                        }
                        .frame(maxWidth: .infinity)
                        .foregroundColor(Color.white)
                        .background(Color.gray)
                        
                        // 循环当前分组用户列表
                        ForEach(section) { item in
                            HStack{
                                Text(item.name ?? "")
                                Spacer()
                                Text(formatDate(date: item.timestamp!))
                            }
                        }
                    }
                }.padding()
            }
        }
        
        // 日期格式化函数
        func formatDate(date: Date) -> String {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            return formatter.string(from: date)
        }
    }

代码执行逻辑:

  1. 我们使用 @SectionedFetchRequest<Int32, User> 来获取分组数据,其泛型 Int32 表示约束 sectionIdentifier 的数据类型为 Int32
  2. 形参 sectionIdentifier 接受一个实体属性,且这个属性必须与上述 @SectionedFetchRequest<Int32, User> 中左侧约束类型一致,代码中传入了 \.score 表示用实体属性 score 的值作为分组
  3. 然后我们通过 ForEach 的嵌套遍历展示分组数据,循环体中的 section.id 为实体属性值

输出结果如下:

image.png

当然我们也可以使用用户名来分组,也就是用 name 属性的值

    // 分组包装器
    @SectionedFetchRequest<String, User>(
        sectionIdentifier: \.name,
        sortDescriptors: [
            NSSortDescriptor(keyPath: \User.score, ascending: false)
        ]
    )
    var users: SectionedFetchResults<String, User>

注意保持泛型的数据类型与分组属性的数据类型一致,这里我们将数据类型替换成 String

聚合与汇总

直接贴代码

    import SwiftUI
    import CoreData

    struct MaxValueView: View {
        // 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
        @Environment(\.managedObjectContext) var viewContext

        var body: some View{
            Text("score最大值为:\(String(fetchMaxValue()!))")
        }
        
        // 获取最大值
        func fetchMaxValue() -> Int32? {
            // 创建一个FetchRequest,结果类型为字典
            let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "User")
            fetchRequest.resultType = .dictionaryResultType

            // 创建一个NSExpression,用于表示"value"属性
            let expression = NSExpression(forKeyPath: "score")

            // 创建一个NSExpression,用于计算平均值
            let averageExpression = NSExpression(forFunction: "max:", arguments: [expression])

            // 创建一个NSExpressionDescription,用于描述计算结果的属性
            let expressionDescription = NSExpressionDescription()
            expressionDescription.name = "maxValue" // 属性的名称
            expressionDescription.expression = averageExpression // 属性的表达式
            expressionDescription.expressionResultType = .integer32AttributeType // 属性的数据类型

            // 将NSExpressionDescription添加到FetchRequest的propertiesToFetch数组中
            fetchRequest.propertiesToFetch = [expressionDescription]

            do {
                // 执行FetchRequest,获取结果
                let result = try viewContext.fetch(fetchRequest)

                // 从结果中提取计算的平均值,并返回
                if let averageValue = result.first?["maxValue"] as? Int32 {
                    return averageValue
                }
            } catch {
                // 处理错误
                print("Error fetching average value: \(error)")
            }

            // 如果发生错误或没有找到结果,则返回nil
            return nil
        }
    }

代码执行逻辑:

  1. 我们通过 NSFetchRequest 创建了查询请求
  2. 使用 NSExpression 的 forKeyPath 指定聚合属性名
  3. 使用 NSExpression 的 forFunction 指定聚合函数,这里也可以替换为 min sum average等函数。
  4. 创建 expressionDescription 结构体,添加到 fetchRequest.propertiesToFetch 数组中
  5. 然后通过 viewContext.fetch 获取到 score 的最大值
  6. 最后在 body 中使用 Text 展示

image.png

总结

  1. 我们了解到Core Data提供了 @SectionedFetchRequest 包装器中的 sectionIdentifier 来指定分组属性实现数据分组
  2. 使用了两个 ForEach 嵌套循环去渲染分组后的结果,其中循环体中的 section.id 为实体属性值
  3. 使用 NSExpression 与 NSExpressionDescription 来指定聚合函数,并添加到 fetchRequest.propertiesToFetch 数组中获取聚合结果

别错过精彩内容,快来关注我们的微信公众号【寻桃】!