1
votes

I have made a very simple app in SwiftUI. ToDo List using Core Data. I can add to do items and store them with Core Data. Items are presented on a list in ContentView. Tapping each item brings us to EditItemView. I have managed to show correct data for each entry. From this view I would like to delete this particular entry I see in EditItemView. It should work similar to deleting lists in Reminders app on iOS. Delete button should delete this particular entry and take us back to ContentView. But... nothing happens. I am not getting any errors, but nothing is deleted as well.

Core Data In Core Data I have 1 entity: ToDoItem (Module: Current Product Module, Codegen: Class Definition) Attributes: createdAt : Date (with default value for today) title: String (with default value = Empty String)

Here is the code I have so far:

ContentView

import SwiftUI

struct ContentView: View {

    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(
        entity: ToDoItem.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \ToDoItem.createdAt, ascending: true),
            NSSortDescriptor(keyPath: \ToDoItem.title, ascending: true)
        ]
    ) var toDoItems: FetchedResults<ToDoItem>

    @State private var show_modal: Bool = false

    var body: some View {
        NavigationView {
            List{
                ForEach(toDoItems, id: \.self) {todoItem in

                    NavigationLink(destination: EditItemView(createdAt: todoItem.createdAt!, title: todoItem.title!)) {

                        ToDoItemView(title: todoItem.title!, createdAt: todoItem.createdAt!)
                    }
                }

            }
            .navigationBarTitle(Text("My List"))
            .navigationBarItems(trailing:
                Button(action: {
                    self.show_modal = true
                }) {
                    Text("Add")
                }.sheet(isPresented: self.$show_modal) {
                    AddItemView().environment(\.managedObjectContext, self.managedObjectContext)
                }
            )
        }
    }
}

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

AddItemView

import SwiftUI

struct AddItemView: View {

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

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

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

    var body: some View {
        NavigationView {
            ScrollView {

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

                    Spacer()
                }

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


                TextField("to do item", text: $title)
                    .font(Font.system(size: 30))

                Spacer()

            }
            .padding()
            .navigationBarTitle(Text("Add transaction"))

            .navigationBarItems(
                leading:
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                },

                trailing:
                Button(action: {
                    let toDoItem = ToDoItem(context: self.managedObjectContext)
                    toDoItem.createdAt = self.createdAt
                    toDoItem.title = self.title

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

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

        }
    }
}

struct AddItemView_Previews: PreviewProvider {
    static var previews: some View {
        AddItemView()
    }
}

EditItemView

import SwiftUI

struct EditItemView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

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

    var createdAt : Date
    var title: String = ""

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

    var body: some View {
        ScrollView {

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

                Spacer()
            }

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


            TextField(title, text: $newTitle)
                .font(Font.system(size: 30))

        }
        .padding()
        .navigationBarTitle(Text("Edit transaction"))
        .navigationBarItems(
            trailing:
            Button(action: {
                print("Delete")

                let deleteToDoItem = ToDoItem(context: self.managedObjectContext)
                self.managedObjectContext.delete(deleteToDoItem)

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

//              let deleteToDoItem = self.toDoItems[indexSet.first!]
//              self.managedObjectContext.delete(deleteToDoItem)
//
//              do {
//                  try self.managedObjectContext.save()
//              }catch{
//                  print(error)
//              }

            }) {
                Text("Delete")
                    .foregroundColor(.red)
            }
        )
    }
}

struct EditItemView_Previews: PreviewProvider {
    static var previews: some View {
        EditItemView(
            createdAt: Date(),
            title: "to do item"
        )
    }
}

ToDoItemView

import SwiftUI

struct ToDoItemView: View {

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

    var title:String = ""
    var createdAt:Date = Date()

    var body: some View {
        HStack{
            VStack(alignment: .leading){
                Text(title)
                    .font(.headline)
                Text("\(createdAt, formatter: Self.dateFormat)")
                    .font(.caption)
            }
        }
    }
}

struct ToDoItemView_Previews: PreviewProvider {
    static var previews: some View {
        ToDoItemView(title: "To do item", createdAt: Date())
    }
}

P.s. I am aware that I can add .onDelete in list view. But I want to make it deliberately harder for the users to delete an item. This is why I want to move Delete button to details view.

2

2 Answers

2
votes

Just add a property to EditItemView

var todoItem: ToDoItem 

Also add the environment object to dismiss the view

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

Edit the delete button action

self.managedObjectContext.delete(self.todoItem)
            do {
                try self.managedObjectContext.save()
                self.presentationMode.wrappedValue.dismiss()
            }catch{
                print(error)
            }

Last on ContentView init the EditView init

ForEach(toDoItems, id: \.self) { todoItem in
EditItemView(todoItem: todoItem)

EDIT:

In EditItemView add:

var todoItem: ToDoItem

And change this line:

Text("\(createdAt, formatter: Self.dateFormat)")

To:

Text(todoItem.createdAt != nil ? "\(todoItem.createdAt!, formatter: Self.dateFormat)" : "")

And this line:

TextField(title, text: $newTitle)

To this:

TextField(todoItem.title != nil ? "\(todoItem.title!)" : "", text: $newTitle)

Thanks to Asperi for helping with this solution here: Error: Argument type 'Date?' does not conform to expected type 'ReferenceConvertible'

0
votes

don't know if its too late, here is my solution:

1.) We name the view from which you call (or, more precisely, instantiate) the Detail View "Caller View". Define in the Caller View a state property to save the reference to the core data entity which has to be deleted:

    @State var entityToDelete: EntityType? = nil

2.) Define in the Detail View the appropriate binding property to the state property above.

    @Binding var entityToDelete: EntityType?

3.) Parameterize the call (instatiation) of Detail View from the Caller View with the new property:

CallerView {
    ...
    DetailView(..., $entityToDelete)
    ...
}

4.) I gas, in the Detail View you present values of some entity and you have an option to delete it (or something similar). In the Detail View set the value of the entityToDelete property to the entity which has to be deleted. It would be probably optimal to dismiss the detail view after klick to "delete button", it depends on your application semantics:

entityToDelete = presentedEntity
    self.presentationMode.wrappedValue.dismiss() // depends on your app logic

5.) Delete entityToDelete in the Caller View. A good place to do that is the .onAppear - closure. If you to that in the Caller View directly, you can have a Warning "View state modification during its actualization....":

CallerView {


} .onAppear (perform: deleteItem)


...

func deleteItem ()->Void {
        if entityToDelete != nil {
            managedObjectContext.delete(entityToDelete!) 
            try? managedObjectContext.save()
            entityToDelete = nil
        }
        
    }

Best, Dragan