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.