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 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! }
}
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")
})
}
}
Deleting CoreData from SwiftUI List with Sections
This is how you would use the link...
Add this to the TodoRowView(todo: todo)
.swipeActions(content: {
Button(role: .destructive, action: {
deleteTodo(todo: todo)
}, label: {
Image(systemName: "trash")
})
})
And you need this method in the View
public func deleteTodo(todo: Todo){
viewContext.delete(todo)
do{
try viewContext.save()
} catch{
print(error)
}
}
Or you can use your current setup that uses onDelete
on the ForEach
.onDelete { indexSet in
deleteTodo(section: Array(section), offsets: indexSet)
}
That uses this method
func deleteTodo(section: [Todo], offsets: IndexSet) {
for index in offsets {
let todo = section[index]
viewContext.delete(todo)
}
try? viewContext.save()
}
And of course for any of this to work you need a working
@Environment(\.managedObjectContext) var viewContext
At the top of your file
Related Topics
Ios-8 and Later - Uitableview Inside an Uialertcontroller
Gcd - Main VS Background Thread for Updating a Uiimageview
Swift Unsafemutablepointer<Unmanaged<Cfstring>> Allocation and Print
Removing Lagging Latency in Drawing Uibezierpath Smooth Lines in Swift
Executefetchrequest Throw Fatal Error: Nsarray Element Failed to Match the Swift Array Element Type
Getting the Action of Uigesturerecognizer in iOS
Fit Image of Random Size into a Uiwebview (Ios)
How to Solve This Exc_Bad_Access(Code=Exc_I386_Gpflt )In Swift Programming
How to Encrypt Using Aes Gcm on iOS
How Is Filters Uiscrollview/Uicollectionview in Apple's Photos App Implemented That It Opens So Fast
How to Emulate Nfc Cards in iOS 13
Swiftui Foreach Index Out of Range Error When Removing Row
How to Ignore Certain Uitouch Points in Multitouch Sequence
Does Swift Not Work with Function Pointers
iOS Facebook Sdk 3.1 Retrieve Friend Birthday Returning Null