How to properly group a list fetched from CoreData by date?
You may try the following, It should work in your situation.
@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")
})
}
}
How to group core data items by date in SwiftUI?
I'm just getting into SwiftUI myself, so this might be a misunderstanding, but I think the issue is that the update
function is unstable, in the sense that it does not guarantee to return the groups in the same order each time. SwiftUI consequently gets confused when new items are added. I found that the errors were avoided by specifically sorting the array:
func update(_ result : FetchedResults<Todo>)-> [[Todo]]{
return Dictionary(grouping: result){ (element : Todo) in
dateFormatter.string(from: element.date!)
}.values.sorted() { $0[0].date! < $1[0].date! }
}
Grouping CoreData by Date() in SwiftUI List as sections
You can make your own sectionIdentifier
in your entity
extension
that works with @SectionedFetchRequest
The return variable just has to return something your range has in common for it to work.
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
}
}
Then use it with your @SectionedFetchRequest
like this
@SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.dueDateRelative, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<String, Todo>
Look at this question too
You can use Date
too but you have to pick a date to be the section header. In this scenario you can use the upperBound date of your range, just the date not the time because the time could create other sections if they don't match.
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
}
}
And then the request will look like this...
@SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.upperBoundDueDate, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<Date, Todo>
Based on the info you have provided you can test this code by pasting the extensions I have provided into a .swift
file in your project and replacing your fetch request with the one you want to use
Grouping FechedResults by Month/Year to display a swiftui list with sections
You can sort the the fetched results if you use a different key in the temporary dictionary that can be properly sorted. So for the DateFormatter used in the function I set the format to "yyyy-MM" instead.
Note that I start by sorting the input to the function but this step is not needed if the fetched result is already sorted on date
which I recommend.
func group(_ result : FetchedResults<LogEntry>) -> [[LogEntry]] {
let sorted = result.sorted { $0.date < $1.date }
return Dictionary(grouping: sorted) { (element : LogEntry) in
dictionaryDateFormatter.string(from: element.date as Date)
}.sorted { $0.key < $1.key }.map(\.value)
}
How to fetch Core Data data from a specific date?
You get the last Monday with the help of Calendar searching backwards for the first occurrence of weekday = 2
let currentDate = Date()
let mondayComponent = DateComponents(weekday: 2)
let lastMonday = Calendar.current.nextDate(after: currentDate,
matching: mondayComponent,
matchingPolicy: .nextTime,
repeatedTimePolicy: .first,
direction: .backward)!
Then create a predicate
let predicate = NSPredicate(format: date >= %@, lastMonday as NSDate)
and apply this predicate to your fetch request.
Side note:
YYYY
is wrong. In a standard calendar use always yyyy
Core Data Group By Year and Sort By Date
- Create an NSFetchRequest. Add a sort descriptor for the date column. That will get your rows in order.
- Execute the fetch request on your managed object context, getting back your results
- Break each date into components (using something like this.
- Iterate, build arrays, whatever you need to do to massage your results into the format you want.
1 will look something like this:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Entry"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"flightDate" ascending:NO];
fetchRequest.sortDescriptors = @[sortDescriptor];
Group by one property while retrieving more with core data
With a proviso that I think this will work; I leave it to you to test it to death:
You cannot add any other attributes to propertiesToFetch
, beyond those in the propertiesToGroupBy
, but it seems you can include the objectID
in the properties to fetch. To do so, build an NSExpression
and associated NSExpressionDescription
for the "evaluated object":
NSExpression *selfExp = [NSExpression expressionForEvaluatedObject];
NSExpressionDescription *selfED = [[NSExpressionDescription alloc] init];
selfED.name = @"objID";
selfED.expression = selfExp;
selfED.expressionResultType = NSObjectIDAttributeType;
Now define another expression/description to get the max(date):
NSExpression *maxDate = [NSExpression expressionForKeyPath:@"date"];
NSExpression *indexExp = [NSExpression expressionForFunction:@"max:" arguments:@[maxDate]];
NSExpressionDescription *maxED = [[NSExpressionDescription alloc] init];
maxED.name = @"maxDate";
maxED.expression = indexExp;
maxED.expressionResultType = NSDateAttributeType;
Then include these two expression descriptions in the list of properties to fetch:
[request setPropertiesToFetch:@[@"phone", maxED, selfED]];
[request setPropertiesToGroupBy:@[@"phone"]];
When you run the fetch, each item in the resulting array will have a key "objID" containing the objectID for the relevant object. You can unpack that, to access the name, phone, etc, with something along these lines:
NSArray *results = [self.context executeFetchRequest:request error:&error];
for (NSDictionary *dict in results) {
NSDate *maxDate = dict[@"maxDate"];
NSString *phone = dict[@"phone"];
NSManagedObjectID *objID = [dict valueForKey:@"objID"];
NSManagedObject *object = [self.context objectWithID:objID];
NSString *name = [object valueForKey:@"name"];
}
One particular aspect I am unsure of is how this will behave if two rows have exactly the same date
.
Related Topics
Present Uialertcontroller from Appdelegate
Uitextview Adding New Line Unintentionally
Exit Application When Click Button - iOS
How to Get a Swift Type Name as a String with Its Namespace (Or Framework Name)
iOS Swift: Could Not Cast Value Type '_Nscfnumber' to 'Nsstring'
Accessing Core Data Stack in Mvvm Application
Flutter: Cocoapods's Specs Repository Is Too Out-Of-Date to Satisfy Dependencies
How to Programmatically Add a Uisegmentedcontrol to a Container View
Swift: Navigate to New Viewcontroller Using Button
How to Execute Some Code After a Segue Is Done
How to Use Objective-C Code with #Define MACros in Swift
Setting Image for Uibarbuttonitem - Image Stretched
Block_Release Deallocating UI Objects on a Background Thread
Swift Compatibility Between Versions for a Library
Swiftui Show/Hide Title Issues with Navigationbar
Cabasicanimation Resets to Initial Value After Animation Completes