【问题标题】:Grouping CoreData by Date() in SwiftUI List as sections在 SwiftUI 列表中按 Date() 将 CoreData 分组为部分
【发布时间】:2022-01-19 20:29:09
【问题描述】:

我的目标:

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

我的尝试...

我尝试使用@SectionedFetchRequest,但 sectionIdentifier 需要一个字符串。如果它作为 Date() 存储在 coreData 中,我该如何转换它以供使用?我收到了许多没有帮助的错误和建议。这也不能解决像“未来 7 天”这样的日期范围。此外,我似乎什至没有访问实体的到期日期,因为它指向我的 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>
Cannot convert value of type 'KeyPath<Todo, Date?>' to expected argument type 'KeyPath<Todo, String>'

Value of type 'NSObject' has no member 'Todo'

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

【问题讨论】:

    标签: ios swift iphone core-data swiftui


    【解决方案1】:

    您可以在与@SectionedFetchRequest 一起使用的entity extension 中创建自己的sectionIdentifier

    返回变量只需要返回你的范围的共同点,它就可以工作。

    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
        }
    }
    

    然后像这样将它与您的@SectionedFetchRequest 一起使用

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

    也看this question

    您也可以使用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
        }
    }
    

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

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

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

    【讨论】:

      【解决方案2】:

      它抛出错误是因为这是你告诉它做的。 @SectionedFetchRequestSectionedFetchResults 发送节标识符和实体类型的元组,因此您指定的SectionedFetchResults 元组必须匹配。就您而言,您写道:

      SectionedFetchResults<String, Todo>
      

      但你想做的是传递一个日期,所以它应该是:

      SectionedFetchResults<Date, Todo>
      

      lorem ipsum 在扩展中使用计算变量来提供节标识符的第二个也是更重要的部分。根据他的回答,你应该回到:

      SectionedFetchResults<String, Todo>
      

      请接受 lorem ipsum 的回答,但要意识到你也需要处理这个问题。

      按“今天”、“明天”、“未来 7 天”等进行分段

      我的建议是使用RelativeDateTimeFormatter,让 Apple 完成大部分或全部工作。要创建一个计算变量来分割,您需要在Todo 上创建一个扩展,如下所示:

      extension Todo {
          
          @objc
          public var sections: String {
              // I used the base Xcode core data app which has timestamp as an optional.
              // You can remove the unwrapping if your dates are not optional.
              if let timestamp = timestamp {
                  // This sets up the RelativeDateTimeFormatter
                  let rdf = RelativeDateTimeFormatter()
                  // This gives the verbose response that you are looking for.
                  rdf.unitsStyle = .spellOut
                  // This gives the relative time in names like today".
                  rdf.dateTimeStyle = .named
      
                  // If you are happy with Apple's choices. uncomment the line below
                  // and remove everything else.
        //        return rdf.localizedString(for: timestamp, relativeTo: Date())
                  
                  // You could also intercept Apple's labels for you own
                  switch rdf.localizedString(for: timestamp, relativeTo: Date()) {
                  case "now":
                      return "today"
                  case "in two days", "in three days", "in four days", "in five days", "in six days", "in seven days":
                      return "this week"
                  default:
                      return rdf.localizedString(for: timestamp, relativeTo: Date())
                  }
              }
              // This is only necessary with an optional date.
              return "undated"
          }
      }
      

      您必须将变量标记为@objc,否则Core Data 将导致崩溃。我认为 Core Data 将是 Obj C 存在的最后一个地方,但我们可以像这样轻松地将 Swift 代码与它接口。

      回到您的视图中,您的@SectionedFetchRequest 看起来像这样:

      @SectionedFetchRequest(
          sectionIdentifier: \.sections,
          sortDescriptors: [NSSortDescriptor(keyPath: \Todo.timestamp, ascending: true)],
          animation: .default)
      private var todos: SectionedFetchResults<String, Todo>
      

      那么您的列表如下所示:

       List {
            ForEach(todos) { section in
                Section(header: Text(section.id.capitalized)) {
                     ForEach(section) { todo in
                     ...
                     }
                }
            }
        }
      

      【讨论】:

      • Lorem ipsum?我只在这个帖子上看到你的答案。并感谢您提供答案。我今晚或明天试试这个。
      • 他发布了一篇文章,涉及处理分段获取请求中的部分的较难部分。我不确定他为什么删除它,但我将不得不重新创建我今天下午正在编写的代码。我会更新这个答案。
      • 我记得看过那个代码,有什么办法让我找回已删除的答案吗?这对这些部分很有帮助。
      • 只有他可以。我更新了关于自定义切片的答案。
      • 我很抱歉。我只是想确保 OP 回答了他的所有问题。
      猜你喜欢
      • 2022-01-20
      • 1970-01-01
      • 2023-03-22
      • 2021-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多