如何按日期正确地对从 CoreData 获取的列表进行分组?

fas*_*soh 14 core-data ios swift swiftui

为简单起见,假设我想创建一个简单的待办事项应用程序。我的 xcdatamodeld 中有一个实体 Todo,其属性为idtitledate,以及以下 swiftui 视图(如图所示):

import SwiftUI

struct ContentView: View {

  @Environment(\.managedObjectContext) var moc
  @State private var date = Date()
  @FetchRequest(
    entity: Todo.entity(),
    sortDescriptors: [
      NSSortDescriptor(keyPath: \Todo.date, ascending: true)
    ]
  ) var todos: FetchedResults<Todo>

  var dateFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    return formatter
  }

  var body: some View {
    VStack {
      List {
        ForEach(todos, id: \.self) { todo in
          HStack {
            Text(todo.title ?? "")
            Text("\(todo.date ?? Date(), formatter: self.dateFormatter)")
          }
        }
      }
      Form {
        DatePicker(selection: $date, in: ...Date(), displayedComponents: .date) {
          Text("Datum")
        }
      }
      Button(action: {
        let newTodo = Todo(context: self.moc)
        newTodo.title = String(Int.random(in: 0 ..< 100))
        newTodo.date = self.date
        newTodo.id = UUID()
        try? self.moc.save()
      }, label: {
        Text("Add new todo")
      })
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

待办事项在获取时按日期排序,并显示在如下列表中:

我得到了什么

我想根据每个待办事项各自的日期对列表进行分组(模型):

我想要的是

根据我的理解,这可以与init()函数中的Dictionaries 一起使用,但是我想不出任何远程有用的东西。有没有一种有效的方法来分组数据?

E.C*_*oms 15

您可以尝试以下操作,它应该适用于您的情况。

  @Environment(\.managedObjectContext) var moc
  @State private var date = Date()
  @FetchRequest(
    entity: Todo.entity(),
    sortDescriptors: [
      NSSortDescriptor(keyPath: \Todo.date, ascending: true)
    ]
  ) var todos: FetchedResults<Todo>

  var dateFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    return formatter
  }

    func update(_ result : FetchedResults<Todo>)-> [[Todo]]{
      return  Dictionary(grouping: result){ (element : Todo)  in
            dateFormatter.string(from: element.date!)
      }.values.map{$0}
    }


  var body: some View {
    VStack {
      List {
        ForEach(update(todos), id: \.self) { (section: [Todo]) in
            Section(header: Text( self.dateFormatter.string(from: section[0].date!))) {
                ForEach(section, id: \.self) { todo in
            HStack {
            Text(todo.title ?? "")
            Text("\(todo.date ?? Date(), formatter: self.dateFormatter)")
            }
            }
          }
        }.id(todos.count)
      }
      Form {
        DatePicker(selection: $date, in: ...Date(), displayedComponents: .date) {
          Text("Datum")
        }
      }
      Button(action: {
        let newTodo = Todo(context: self.moc)
        newTodo.title = String(Int.random(in: 0 ..< 100))
        newTodo.date = self.date
        newTodo.id = UUID()
        try? self.moc.save()
      }, label: {
        Text("Add new todo")
      })
    }
  }
Run Code Online (Sandbox Code Playgroud)

  • pbasdf 帮助我解决了这个问题。一切功劳都归于他。将 `}.values.map{$0}` 更改为 `}.values.sorted() { $0[0].date!&lt; $1[0].日期!}` 使其可以与 .sheet 一起使用,并且错误消失了。 (3认同)

Pra*_*tti 7

iOS 15 更新

SwiftUI现在List通过@SectionedFetchRequest属性包装器内置了对分段提取请求的支持。这个包装器减少了对核心数据列表进行分组所需的样板数量。

示例代码

@Environment(\.managedObjectContext) var moc
@State private var date = Date()
@SectionedFetchRequest( // Here we use SectionedFetchRequest
  entity: Todo.entity(),
  sectionIdentifier: \.dateString // Add this line
  sortDescriptors: [
    SortDescriptor(\.date, order: .forward)
  ]
) var todos: SectionedFetchedResults<Todo>

var body: some View {
    VStack {
      List {
        ForEach(todos) { (section: [Todo]) in
            Section(section[0].dateString!))) {
                ForEach(section) { todo in
                    HStack {
                        Text(todo.title ?? "")
                        Text("\(todo.date ?? Date(), formatted: todo.dateFormatter)")
                    }
                }
            }
        }.id(todos.count)
      }
      Form {
        DatePicker(selection: $date, in: ...Date(), displayedComponents: .date) {
          Text("Datum")
        }
      }
      Button(action: {
        let newTodo = Todo(context: self.moc)
        newTodo.title = String(Int.random(in: 0 ..< 100))
        newTodo.date = self.date
        newTodo.id = UUID()
        try? self.moc.save()
      }, label: {
        Text("Add new todo")
      })
    }
Run Code Online (Sandbox Code Playgroud)

Todo班还可以重构包含逻辑用于获取日期字符串。作为奖励,我们还可以使用.formattedbeta 方法Date来生成相关的String.

struct Todo {
  
  ...

  var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    return formatter
  }()

  var dateString: String? {
    formatter.string(from: date)
  }
}
Run Code Online (Sandbox Code Playgroud)


Gen*_*ich 5

要将 Core Data 支持的 SwiftUI List 划分为多个部分,您可以更改数据模型以支持分组。TodoSection在这种特殊情况下,这可以通过将实体引入托管对象模型来实现。该实体将具有date用于对节进行排序的属性和name用作节 ID 的唯一字符串属性,以及节标题名称。可以通过对托管对象使用核心数据唯一约束来强制执行唯一质量。每个部分中的待办事项都可以建模为与您的实体的一对多Todo关系。

\n

保存新Todo对象时,您必须使用“查找”或“创建”模式来查明您是否已存储了一个部分,或者必须创建一个新部分。

\n
    let sectionName = dateFormatter.string(from: date)\n    let sectionFetch: NSFetchRequest<TodoSection> = TodoSection.fetchRequest()\n    sectionFetch.predicate = NSPredicate(format: "%K == %@", #keyPath(TodoSection.name), sectionName)\n    \n    let results = try! moc.fetch(sectionFetch)\n    \n    if results.isEmpty {\n        // Section not found, create new section.\n        let newSection = TodoSection(context: moc)\n        newSection.name = sectionName\n        newSection.date = date\n        newSection.addToTodos(newTodo)\n    } else {\n        // Section found, use it.\n        let existingSection = results.first!\n        existingSection.addToTodos(newTodo)\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

要显示您的部分和随附的待办事项,ForEach可以在两者之间使用嵌套视图Section。Core Data 使用NSSet?多种关系,因此您必须使用数组代理并遵守Todo所有Comparable内容才能与 SwiftUI 一起使用。

\n
    extension TodoSection {\n        var todosArrayProxy: [Todo] {\n            (todos as? Set<Todo> ?? []).sorted()\n        }\n    }\n    \n    extension Todo: Comparable {\n        public static func < (lhs: Todo, rhs: Todo) -> Bool {\n            lhs.title! < rhs.title!\n        }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

如果您需要删除某个待办事项,请记住,节中最后删除的待办事项也应该删除整个节对象。

\n

我尝试init(grouping:by:)使用Dictionary,正如这里所建议的那样,就我而言,它会导致锯齿状动画,这可能是我们走错方向的标志。I\xe2\x80\x99m 猜测当我们删除单个项目时必须重新编译整个项目列表。此外,随着数据集的增长,将分组嵌入到数据模型中将更加高效且面向未来。

\n

如果您需要任何进一步的参考,我提供了一个示例项目。

\n