【问题标题】:SwiftUI: How to sum CoreData with dynamic filters?SwiftUI:如何将 CoreData 与动态过滤器相加?
【发布时间】:2020-01-13 12:09:13
【问题描述】:

在另一个问题中,我发布了一个使用过滤器汇总 CoreData 的解决方案。 问题是:我无法使这些过滤器动态化。

我有一个 CoreData 实体:NPTransaction,其属性:date (Date)value (Integer 64)

使用静态过滤器求和的工作代码:

import SwiftUI
import CoreData

struct DashboardView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    // FetchRequest with predicate set to "after now" 
    @FetchRequest(entity: NPTransaction.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)], predicate: NSPredicate(format: "date > %@", Date() as NSDate)) var fetchRequest: FetchedResults<NPTransaction>

    // sum results using reduce
    var sum: Int64 {
        fetchRequest.reduce(0) { $0 + $1.value }
    }

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {

                HStack {
                    VStack(alignment: .leading) {
                        Text("sum")
                        Text("\(sum)")
                            .font(.largeTitle)

                    }
                    Spacer()
                }
                .padding()

                Spacer()
            }.navigationBarTitle("Title")
        }
    }

}

struct DashboardView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return DashboardView().environment(\.managedObjectContext, context)
    }
}

现在,如果我尝试像这样添加 selectedDate 变量:

// (...)

struct DashboardView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @State var selectedDate = Date()

    @FetchRequest(entity: NPTransaction.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)], predicate: NSPredicate(format: "date > %@", selectedDate as NSDate)) var fetchRequest: FetchedResults<NPTransaction>

// (...)

我收到一个错误:

不能在属性初始化器中使用实例成员“selectedDate”; 属性初始化器在“self”可用之前运行

所以我尝试将 FetchRequest 移动到稍后在代码中进行初始化,如下所示:

import SwiftUI
import CoreData

struct DashboardView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @State var selectedDate = Date()

    var fetchRequest: FetchRequest<NPTransaction>

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {

                HStack {
                    VStack(alignment: .leading) {
                        Text("sum")
                        Text("\(sum)")
                            .font(.largeTitle)

                    }
                    Spacer()
                }
                .padding()

                Spacer()
            }.navigationBarTitle("Title")
        }
    }

    init() {
        fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)
        ], predicate: NSPredicate(format: "date >= %@", Date() as NSDate))

        var sum: Int64 {
            fetchRequest.reduce(0) { $0 + $1.value }
        }
    }

}

但现在我收到 2 个错误。一个与reduce一致:

“FetchRequest”类型的值没有成员“reduce”

还有一个与 Text("(sum)") :

使用未解析的标识符“sum”

如何解决这个问题?如何将 CoreData FetchRequest 与用户可以更改的动态过滤器相加?


解决方案 根据 Asperi 和 Aspid 的回答,这里是使动态过滤器与 CoreData 值的总和一起工作的最终代码:

DashboardView.swift

import SwiftUI

struct DashboardView: View {
    @State var selectedDate = Date()

    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {

                // selectedDate change is controlled in another DateView
                DateView(selectedDate: $selectedDate)

                // selectedDate is passed to init in DashboardViewSum
                DashboardViewSum(selectedDate: selectedDate)

                Spacer()
            }.navigationBarTitle("Title")
        }
    }
}

struct DashboardView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return DashboardView().environment(\.managedObjectContext, context)
    }
}

DashboardViewSum.swift

import SwiftUI

struct DashboardViewSum: View {

    @Environment(\.managedObjectContext) var managedObjectContext   
    var fetchRequest: FetchRequest<NPTransaction>
    var sum: Int64 {
        fetchRequest.wrappedValue.reduce(0) { $0 + $1.value }
    }

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("sum")
                Text("\(sum)")
                    .font(.largeTitle)

            }
            Spacer()
        }
        .padding()
    }

    // selectedDate is passed from DashboardView and makes predicate dynamic. Each time it is changed, DashboardViewSum is reinitialized
    init(selectedDate: Date) {
        fetchRequest = FetchRequest<NPTransaction>(entity: NPTransaction.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \NPTransaction.value, ascending: false)
        ], predicate: NSPredicate(format: "date >= %@", selectedDate as NSDate))
    }

}

struct DashboardViewSum_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return DashboardViewSum(selectedDate: Date()).environment(\.managedObjectContext, context)
    }
}

【问题讨论】:

    标签: ios swift xcode core-data swiftui


    【解决方案1】:

    如果要在 Init 中执行 fetch,则需要先获取上下文。 @Environment(\.managedObjectContext) var managedObjectContext 对你没有帮助,因为它还没有准备好。 @FetchRequest 是一个包装器,它允许您在需要结果时进行填充,但在您定义它时它不会执行获取。 这段代码对我有用,但我不确定以这种方式获取上下文是否真的正确并且在所有情况下都很好用:

    init(){
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContaner.viewContext
        let fetchRequest: NSFetchRequest<SomeType> = SomeType.fetchRequest
        //... some order and filter
        if let result = try? context.fetch(fetchRequest){
            //result is [someClass] - do what you need
        }
    }
    

    但是还有一个选择。 您可以在 subView 中提取 Sum 视图,并将 date 变量传递给您使用谓词的init。当您更改 @State 日期时,subView 将再次初始化,并且 fetch 将以新日期执行。这样看起来好多了。

    【讨论】:

    • 非常感谢您,阿斯皮德!我无法让你的代码工作,但你的替代想法帮助我解决了这个问题。杰出的!我已将您的想法与 Asperi 对我的代码的修复一起使用,现在它可以工作了:) 我已经编辑了我的问题,以根据您和 Asperi 的答案提供有效的解决方案。谢谢!
    【解决方案2】:

    我认为它的目的是将sum 设为可计算的DashboardView 属性(在init 之外),作为

    var sum: Int64 {
        fetchRequest.wrappedValue.reduce(0) { $0 + $1.value }
    }
    
    ...
    init() {
       ...
    }
    

    【讨论】:

    • 非常感谢您,Asperi!你的代码帮助我摆脱了错误,让我离解决方案更近了一步。 Aspid 的想法与您的修复相结合,使其发挥了作用。我已经编辑了我的问题,以根据您和 Aspid 的回答提供可行的解决方案。谢谢!
    猜你喜欢
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多