I have a conceptual question regarding plotting charts in SwiftUI based on data from core data. Lets say I am building a todo list app and I have a todo entity in core data. This entity has the attributes name and finishDate (the date on which the todo was marked as finished).
To plot these two variables, I need a variable containing each individual day and a variable containing the number of finished tasks on each specific day.
Does anyone know how I would go about creating this data efficiently? I know I can fetch the todo entity data and select the correct attributes. But how do I get the number of finished tasks on each specific day? Ideally without creating those variables using for loops.
It would be really appreciated if anyone can help me.
CodePudding user response:
This is easy using CoreData SwiftUI.
The code below is for iOS15 but you can do the same thing with an NSFetchedResultsController
or NSFetchRequest
/ @FetchRequest
and then group it. But it will require a bit of effort to stay real time.
Also, the code below is meant to work with the standard Apple code for a CoreData project. The only thing I changed in PersistenceController
is setting a random day for the timestamp
newItem.timestamp = Date().addingTimeInterval(60*60*24*Double(Int.random(in: -10...10)))
This is a simple graph
import SwiftUI
@available(iOS 15.0, *)
struct DayChart: View {
@SectionedFetchRequest(entity: Item.entity(), sectionIdentifier: \.completionDate, sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<String, Item>
@State var maxCount: Int = 1
let spacing: CGFloat = 3
var body: some View {
VStack{
GeometryReader{ geo in
HStack(alignment: .bottom, spacing: spacing){
ForEach(sections){section in
VStack{
Text(section.count.description)
Rectangle()
.foregroundColor(.blue)
.onAppear(){
maxCount = max(maxCount,section.count)
}
Text(section.id.description).minimumScaleFactor(0.5)
.lineLimit(2)
}.frame(width: (geo.size.width/CGFloat(sections.count) - spacing),height: geo.size.height * CGFloat(CGFloat(section.count)/CGFloat(maxCount)))
}
}
}
}.padding(.leading, spacing)
}
}
@available(iOS 15.0, *)
struct DayChart_Previews: PreviewProvider {
static var previews: some View {
DayChart().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
extension Item{
//This is the variable that determines your section/column/completion date
@objc
var completionDate: String{
if self.timestamp != nil{
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd\nMMM"
return dateFormatter.string(from: self.timestamp!)
}else{
return ""
}
}
}
CodePudding user response:
I’d start with an attribute for the day of the week (which you can derive from the Date()
value you’re using for completion date). Then you can use either a count fetch request (Cocoa Core Data efficient way to count entities), or a group-by and an NSDictionaryResultType
(extensive code example at https://www.cocoanetics.com/2017/04/group-by-count-and-sum-in-coredata/). The group-by approach will give you a dictionary that looks like this (from the Cocoanetics article):
[
{
"count": 111,
"col": "One"
},
{
"count": 222,
"col": "Two"
}
]
I believe, but don’t know for certain, that you’ll have to compute/store the day-of-week value as an attribute for these grouping approaches to work.