在 SwiftUI 列表中按 Date() 将 CoreData 分组为部分

Jus*_*ock 2 iphone core-data ios swift swiftui

我的目标:

我希望能够按其到期日期范围对 CoreData Todo 项目进行分组。(“今天”、“明天”、“接下来 7 天”、“未来”)

我尝试过的...

我尝试使用@SectionedFetchRequest,但sectionIdentifier需要一个字符串。如果它作为 Date() 存储在 coreData 中,如何将其转换以供使用?我收到了许多没有帮助的错误和建议。这也无法解决“接下来 7 天”等日期范围的问题。此外,我似乎甚至没有访问实体的 dueDate,因为它指向我的 ViewModel 表单。

    @Environment(\.managedObjectContext) private var viewContext
    
    //Old way of fetching Todos without the section fetch
    //@FetchRequest(sortDescriptors: []) var todos: FetchedResults<Todo>
    
    @SectionedFetchRequest<String, Todo>(
        entity: Todo.entity(), sectionIdentifier: \Todo.dueDate,
        SortDescriptors: [SortDescriptor(\.Todo.dueDate, order: .forward)]
    ) var todos: SectionedFetchResults<String, Todo>
Run Code Online (Sandbox Code Playgroud)
Cannot convert value of type 'KeyPath<Todo, Date?>' to expected argument type 'KeyPath<Todo, String>'

Value of type 'NSObject' has no member 'Todo'
Run Code Online (Sandbox Code Playgroud)

是否有另一种解决方案更适合我的情况,@SectionedFetchRequest?如果没有,我希望了解如何适当地对数据进行分组。

lor*_*sum 8

sectionIdentifier您可以在您的entity extension作品中制作自己的作品@SectionedFetchRequest

返回变量只需要返回您的范围的共同点即可工作。

extension Todo{
    ///Return the string representation of the relative date for the supported range (year, month, and day)
    ///The ranges include today, tomorrow, overdue, within 7 days, and future
    @objc
    var dueDateRelative: String{
        var result = ""
        if self.dueDate != nil{
            //Order matters here so you can avoid overlapping
            if Calendar.current.isDateInToday(self.dueDate!){
                result = "today"//You can localize here if you support it
            }else if Calendar.current.isDateInTomorrow(self.dueDate!){
                result = "tomorrow"//You can localize here if you support it
            }else if Calendar.current.dateComponents([.day], from: Date(), to: self.dueDate!).day ?? 8 <= 0{
                result = "overdue"//You can localize here if you support it
            }else if Calendar.current.dateComponents([.day], from: Date(), to: self.dueDate!).day ?? 8 <= 7{
                result = "within 7 days"//You can localize here if you support it
            }else{
                result = "future"//You can localize here if you support it
            }
        }else{
            result =  "unknown"//You can localize here if you support it
        }
        return result
    }
}
Run Code Online (Sandbox Code Playgroud)

@SectionedFetchRequest然后像这样使用它

@SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.dueDateRelative, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<String, Todo>
Run Code Online (Sandbox Code Playgroud)

也看看这个问题

您也可以使用Date,但必须选择一个日期作为节标题。在这种情况下,您可以使用范围的上限日期,仅使用日期而不是时间,因为如果时间不匹配,时间可能会创建其他部分。

extension Todo{
    ///Return the upperboud date of the available range (year, month, and day)
    ///The ranges include today, tomorrow, overdue, within 7 days, and future
    @objc
    var upperBoundDueDate: Date{
        //The return value has to be identical for the sections to match
        //So instead of returning the available date you return a date with only year, month and day
        //We will comprare the result to today's components
        let todayComp = Calendar.current.dateComponents([.year,.month,.day], from: Date())
        var today = Calendar.current.date(from: todayComp) ?? Date()
        if self.dueDate != nil{
            //Use the methods available in calendar to identify the ranges
            //Today
            if Calendar.current.isDateInToday(self.dueDate!){
                //The result variable is already setup to today
                //result = result
            }else if Calendar.current.isDateInTomorrow(self.dueDate!){
                //Add one day to today
                today = Calendar.current.date(byAdding: .day, value: 1, to: today)!
            }else if Calendar.current.dateComponents([.day], from: today, to: self.dueDate!).day ?? 8 <= 0{
                //Reduce one day to today to return yesterday
                today = Calendar.current.date(byAdding: .day, value: -1, to: today)!
            }else if Calendar.current.dateComponents([.day], from: today, to: self.dueDate!).day ?? 8 <= 7{
                //Return the date in 7 days
                today = Calendar.current.date(byAdding: .day, value: 7, to: today)!
            }else{
                today = Date.distantFuture
            }
        }else{
            //This is something that needs to be handled. What do you want as the default if the date is nil
            today = Date.distantPast
        }
        return today
    }
}
Run Code Online (Sandbox Code Playgroud)

然后请求将如下所示......

@SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.upperBoundDueDate, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<Date, Todo>
Run Code Online (Sandbox Code Playgroud)

根据您提供的信息,您可以通过将我提供的扩展粘贴到.swift项目中的文件中并将您的提取请求替换为您想要使用的扩展来测试此代码