提问人:Justin Comstock 提问时间:12/17/2021 更新时间:1/11/2023 访问量:2510
在 SwiftUI 中按 Date() 对 CoreData 进行分组 列表为部分
Grouping CoreData by Date() in SwiftUI List as sections
问:
我的目标:
我希望能够按 dueDate 范围对 CoreData Todo 项进行分组。(“今天”、“明天”、“未来 7 天”、“未来”)
我尝试了什么......
我尝试使用,但 sectionIdentifier 需要一个字符串。如果它以 Date() 的形式存储在 coreData 中,我该如何转换它以供使用?我收到了许多错误和建议,但都没有帮助。这也不能解决“未来 7 天”等日期范围。此外,我似乎甚至没有访问实体的 dueDate,因为它指向我的 ViewModel 表单。@SectionedFetchRequest
@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?
答:
您可以在sectionIdentifier
entity
extension
@SectionedFetchRequest
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>
也看看这个问题
您也可以使用,但必须选择一个日期作为部分标题。在此方案中,可以使用范围的 upperBound 日期,仅使用日期而不是时间,因为如果时间不匹配,则时间可能会创建其他部分。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
它抛出错误,因为这是你告诉它要做的。 将部分标识符和实体类型的元组发送到 ,因此您指定的元组必须匹配。就你而言,你写道:@SectionedFetchRequest
SectionedFetchResults
SectionedFetchResults
SectionedFetchResults<String, Todo>
但是你要做的是传递一个日期,所以它应该是:
SectionedFetchResults<Date, Todo>
Lorem ipsum 击败了我,在扩展中使用计算变量来提供部分标识符的第二个,也是更重要的部分。根据他的回答,你应该回到:
SectionedFetchResults<String, Todo>
请接受 lorem ipsum 的回答,但要意识到您也需要处理这个问题。
按“今天”、“明天”、“未来 7 天”等进行分段。
我的建议是使用 Apple 并让 Apple 完成大部分或全部工作。要创建一个计算变量来分割,你需要创建一个扩展,如下所示:RelativeDateTimeFormatter
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"
}
}
您必须将变量标记为 ,否则 Core Data 将导致崩溃。我认为 Core Data 将是 Obj C 的最后一个地方,但我们可以很容易地像这样将 Swift 代码与它连接起来。@objc
回到您的视图中,您的外观如下所示:@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
...
}
}
}
}
评论
您可以使用此方法来实现: 喜欢这个:
func formattedDate () -> String? {
let RFC3339DateFormatter = DateFormatter()
RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
RFC3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let date1 = RFC3339DateFormatter.date(from: date.formatted()) ?? Date()
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
// ES Spanish Locale (es_ES)
dateFormatter.locale = Locale.current//Locale(identifier: "es_ES")
return dateFormatter.string(from: date1) // Jan 2, 2001
}
评论