在 SwiftUI 列表中按 Date() 分组 CoreData 作为部分
Grouping CoreData by Date() in SwiftUI List as sections
我的目标:
我希望能够按截止日期范围对 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?
更适合我的情况,如果没有,我想了解如何适当地对数据进行分组。
您可以在 entity
extension
中创建自己的 sectionIdentifier
并与 @SectionedFetchRequest
一起使用
return 变量只需要 return 你的范围有共同点就可以工作。
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>
您也可以使用 Date
,但您必须选择一个日期作为 header 部分。在这种情况下,您可以使用范围的上限日期,只是日期而不是时间,因为如果时间不匹配,时间可能会创建其他部分。
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
文件并将您的获取请求替换为您要使用的文件来测试此代码
它正在抛出错误,因为这是您告诉它要做的。 @SectionedFetchRequest
将段标识符和实体类型的元组发送到 SectionedFetchResults
,因此您指定的 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
...
}
}
}
}
我的目标:
我希望能够按截止日期范围对 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?
更适合我的情况,如果没有,我想了解如何适当地对数据进行分组。
您可以在 entity
extension
中创建自己的 sectionIdentifier
并与 @SectionedFetchRequest
return 变量只需要 return 你的范围有共同点就可以工作。
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>
您也可以使用 Date
,但您必须选择一个日期作为 header 部分。在这种情况下,您可以使用范围的上限日期,只是日期而不是时间,因为如果时间不匹配,时间可能会创建其他部分。
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
文件并将您的获取请求替换为您要使用的文件来测试此代码
它正在抛出错误,因为这是您告诉它要做的。 @SectionedFetchRequest
将段标识符和实体类型的元组发送到 SectionedFetchResults
,因此您指定的 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
...
}
}
}
}