skip to Main Content

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.

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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 = "ddnMMM"
                return dateFormatter.string(from: self.timestamp!)
                 
            }else{
                return ""
            }
        }
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search