0

I am designing an app where users will key in information in an alert view with multiple textfields. These strings will then be combined and be saved in an attribute of an entity (as one entity). This will then be displayed in a tableview.

Now I would implement a way to delete entries should the user enter something wrong.

I have seen many places where they say use the following code to delete entries in CoreData:

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
    let managedObjectContext = coreData.persistentContainer.viewContext

    if editingStyle == .delete
    {
        movieToDelete = fetchedResultController.object(at: indexPath)

        let confirmDeleteAlertController = UIAlertController(title: "Remove Movie", message: "Are you sure you would like to delete \"\(movieToDelete!.title!)\" from your movie library?", preferredStyle: UIAlertControllerStyle.actionSheet)

        let deleteAction = UIAlertAction(title: "Delete", style: UIAlertActionStyle.default, handler: { [weak self] (action: UIAlertAction) -> Void in
            managedObjectContext.delete((self?.movieToDelete!)!)
            self?.coreData.saveContext()
            self?.movieToDelete = nil
        })

        let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { [weak self] (action: UIAlertAction) -> Void in
            self?.movieToDelete = nil
        })

        confirmDeleteAlertController.addAction(deleteAction)
        confirmDeleteAlertController.addAction(cancelAction)

        present(confirmDeleteAlertController, animated: true, completion: nil)

    }
}

However, I was following a tutorial on youtube and as a result I do not have a ManagedObjectContext in my code to make use of the editingStyle.delete. Now I am stuck as to how to delete an entry. Do I change my earlier code to support a ManagedObjectContext or is there a way to delete the entries through persistent container?

This is the code in my viewController which I feel is important to the issue at hand:

class ViewController: UITableViewController {

    var alarmItems: [NSManagedObject] = []
    let cellId = "cellId"


    override func viewDidLoad() {
        super.viewDidLoad()



    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "AlarmItems")
        do {
            alarmItems = try managedContext.fetch(fetchRequest)
        } catch let err as NSError {
            print("Failed to fetch items", err)
        }
    }

    @objc func addAlarmItem(_ sender: AnyObject) {
        print("this works")
        let alertController = UIAlertController(title: "Add New Item", message: "Please fill in the blanks", preferredStyle: .alert)
        let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in

            //combined string of attributes
            let myStrings: [String] = alertController.textFields!.compactMap { $0.text }
            let myText = myStrings.joined(separator: ", ")

            self.save(myText)
            self.tableView.reloadData()
        }
        let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)

        alertController.addTextField { (textField) in
            textField.placeholder = "Enter Name of Engineer"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Enter Date of Alarm in DD/MM/YYYY"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Enter Time of Alarm in 24h (eg: 2300)"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Please indicate True/False (type True or False)"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Insert comments (if any), or NIL"
        }


        alertController.addAction(saveAction)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }

    func save(_ itemName: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "AlarmItems", in: managedContext)!
        let item = NSManagedObject(entity: entity, insertInto: managedContext)
        item.setValue(itemName, forKey: "alarmAttributes")

        do {
            try managedContext.save()
            alarmItems.append(item)

        } catch let err as NSError {
            print("Failed to save an item", err)
        }
    }

    @objc func exportCSV(_ sender: AnyObject) {
        //will work on exporting csv in the future
        return
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return alarmItems.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
        let alarmItem = alarmItems[indexPath.row]
        cell.textLabel?.text = alarmItem.value(forKeyPath: "alarmAttributes") as? String
        return cell
    }

    /*
    //create delete feature

   override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath ) {
        if editingStyle == UITableViewCell.EditingStyle.delete {


        }
    }
 */

}
Marwen Doukh
  • 1,946
  • 17
  • 26
danaq
  • 117
  • 11
  • Your question is unclear since you ask if you should "support a ManagedObjectContextor" but your code already uses a NSManagedObjectContext object and that is of course no surprise since this class is one of the most central in Core Data. – Joakim Danielson Jun 18 '19 at 06:48
  • I tried to do do managedContext.delete but that did not work because xcode cannot identify what managedContext is (it is my NSManagedObjectContext) so i figured there might be an error with my code or how I call for the context. Do I have to declare a new managedobjectcontext in my new function for deleting entries? – danaq Jun 18 '19 at 07:06
  • Hey Please check this url :- https://stackoverflow.com/a/38018674/8201581. – Yogesh Patel Jun 18 '19 at 07:52

1 Answers1

0

As you are using NSFetchedResultsController delete the data source array

var alarmItems: [NSManagedObject] = []  

and all other occurrences of alarmItems. The NSFetchedResultsController instance becomes the data source array

In viewWillAppear just refetch the data

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    do {
        try fetchedResultsController.performFetch()
        tableView.reloadData()
    } catch {
        print(error)
    }
}

Replace the datasource and delegate methods with

override func numberOfSections(in tableView: UITableView) -> Int {
    return fetchedResultsController.sections?.count ?? 0
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let sectionInfo = fetchedResultsController.sections![section]
    return sectionInfo.numberOfObjects
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
    let alarmItem = fetchedResultsController.object(at: indexPath) as! NSManagedObject
    cell.textLabel?.text = alarmItem.value(forKeyPath: "alarmAttributes") as? String
    return cell
}

Now your delete method is supposed to work.

Note:

You are encouraged to use always the NSManagedObject subclass AlarmItems ( by the way semantically each Core Data record represents one AlarmItem) and dot notation for example

let alarmItem = fetchedResultsController.object(at: indexPath) as! AlarmItem
cell.textLabel?.text = alarmItem.alarmAttributes
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Ah! So basically, what I can do is remove all instances of alarmItems and replace it with NSFetchedResultsController because the controller already acts as the CoreData entity? Thank you so much for your help, I will try it out! – danaq Jun 18 '19 at 07:13
  • Please read the [`NSFetchedResultsController` documentation](https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller) and [here](https://cocoacasts.com/populate-a-table-view-with-nsfetchedresultscontroller-and-swift-3) is a tutorial how to use `NSFetchedResultsController` – vadian Jun 18 '19 at 07:17
  • I have tried to implement what you have told me to (thank you very much btw) but now my app crashes. I will ask another question soon. – danaq Jun 19 '19 at 06:46