How to properly group CoreData records by category in SwiftUI?

Viewed 369

I am using CoreData. I have:

  • 1 Entity: Todo
  • 3 attributes: category (String), date (Date), title (String).
  • Module: Current Product Module
  • Codegen: Class Definition

I would like to build a simple ToDo app that looks like this:

To do items

Category
1. 3/26/20 To do item 1
2. 3/26/20 To do item 2

Category
1. 3/26/20 To do item 3
2. 3/27/20 To do item 4

I know there are similar questions, but I didn't find an answer on how to set everything using CoreData and SwiftUI.

I have most of the code done. Including adding items, saving to CoreData, deleting items.

ContentView.swift Here I display the To do list. I have added the comments to problematic parts.

import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @State private var date = Date()
    @FetchRequest(
        entity: Todo.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \Todo.date, ascending: true)
        ]
    ) var todos: FetchedResults<Todo>

    @State private var show_modal: Bool = false

//  let dictionary = Dictionary(grouping: Todo) { $0.category }

    var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        return formatter
    }

    var body: some View {
        NavigationView {
            List {

                // Here I should sort by category
                ForEach(self.todos, id: \.title) { todo in

                    // Here I should write category name instead of static text
                    Section(header: Text("Category")) {
                        ForEach(Array(self.todos.enumerated()), id: \.element) {(i, todo) in

                            NavigationLink(destination: TodoDetailsView(todo: todo)) {
                                HStack {
                                    Text("\(i+1). ")
                                    Text("\(todo.date ?? Date(), formatter: self.dateFormatter)")
                                    Text(todo.title ?? "")
                                }
                            }
                        }
                    }
                }

            }
            .navigationBarTitle(Text("To do items"))
            .navigationBarItems(
                trailing:
                Button(action: {
                    self.show_modal = true
                }) {
                    Text("Add")
                }.sheet(isPresented: self.$show_modal) {
                    TodoAddView().environment(\.managedObjectContext, self.moc)
                }
            )
                .listStyle(GroupedListStyle())
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
    }
}

TodoAddView.swift Here I add new items and save them to CoreData. This works OK.

import SwiftUI

struct TodoAddView: View {

    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) var moc

    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter
    }()

    @State private var showDatePicker = false
    @State private var title = ""
    @State private var category = ""
    @State private var date : Date = Date()

    var body: some View {
        NavigationView {

            VStack {
                HStack {
                    Button(action: {
                        self.showDatePicker.toggle()
                    }) {
                        Text("\(date, formatter: Self.dateFormat)")
                    }

                    Spacer()
                }

                if self.showDatePicker {
                    DatePicker(
                        selection: $date,
                        displayedComponents: .date,
                        label: { Text("Date") }
                    )
                        .labelsHidden()
                }

                TextField("title", text: $title)
                TextField("category", text: $category)

                Spacer()

            }
            .padding()
            .navigationBarTitle(Text("Add to do item"))
            .navigationBarItems(
                leading:
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                },

                trailing:
                Button(action: {

                    let todo = Todo(context: self.moc)
                    todo.date = self.date
                    todo.title = self.title
                    todo.category = self.category

                    do {
                        try self.moc.save()
                    }catch{
                        print(error)
                    }

                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Done")
                }
            )
        }
    }
}

struct TodoAddView_Previews: PreviewProvider {
    static var previews: some View {
        TodoAddView()
    }
}

TodoDetailsView.swift Here I display details of each item and can delete an item from there. This works good too.

import SwiftUI

struct TodoDetailsView: View {

    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var todo: Todo

    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter
    }()

    @State private var showDatePicker = false
    @State private var newDate : Date = Date()
    @State private var newTitle = ""
    @State private var newCategory = ""

    var body: some View {
        ScrollView {

            VStack {
                HStack {
                    Button(action: {
                        self.showDatePicker.toggle()
                    }) {
                        Text("\(newDate, formatter: Self.dateFormat)")
                    }

                    Spacer()
                }

                if self.showDatePicker {
                    DatePicker(
                        selection: $newDate,
                        displayedComponents: .date,
                        label: { Text("Date") }
                    )
                        .labelsHidden()
                }

                TextField("title", text: $newTitle, onCommit: {
                    self.todo.title = self.newTitle
                    try? self.moc.save()
                }
                )

                TextField("category", text: $newCategory, onCommit: {
                    self.todo.category = self.newCategory
                    try? self.moc.save()
                }
                )

                Spacer()

            }
            .padding()
            .navigationBarTitle(Text("Details"))
            .navigationBarItems(
                trailing:
                Button(action: {
                    self.moc.delete(self.todo)
                    do {
                        try self.moc.save()
                        self.presentationMode.wrappedValue.dismiss()
                    }catch{
                        print(error)
                    }
                }) {
                    Text("Delete")
                        .foregroundColor(.red)
                }
            )
        }
        .onAppear {
            self.newDate = self.todo.date ?? Date()
            self.newTitle = self.todo.title ?? ""
            self.newCategory = self.todo.category ?? ""
        }
        .onDisappear {
            self.todo.date = self.newDate
            self.todo.title = self.newTitle
            self.todo.category = self.newCategory
            try? self.moc.save()
        }
    }
}

struct TodoDetailsView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let todo = Todo.init(context: context)
        todo.date = Date()
        todo.title = "to do item"
        return TodoDetailsView(todo: todo).environment(\.managedObjectContext, context)
    }
}

The thing I don't know is how to properly group the items by, for example, category. How to do it using Core Data? I assume I need to change Codegen of my Entity and add an extension to group the records. For example something like this:

let dictionary = Dictionary(grouping: Todo) { $0.category }

But should it be Manual/None or Category/Extension? And where and which code to use? I know how to create a Subclass.

And later I could probably reference to this dictionary in ForEach in my ContentView. But don't know how exactly.

If my assumptions are wrong, please correct me. Thanks in advance.

0 Answers
Related