Search code examples
iosswiftcore-datacrashnsfetchedresultscontroller

My Xcode App crashes after trying to implement NSFetchedResultsController due to uncaught exception 'NSInvalidArgumentException'


I recently asked a question on how to implement a delete function for my CoreData entities. (here:How do I delete CoreData entries in a tableview that uses a persistentcontainer?) I was informed that I should make use of the NSFetchResultsController so I began to work on that. I thought I got it right my code now crashes immediately after running.

I have tried using a tutorial to implement the FetchedResultsController. I am pretty sure I have followed all the steps correctly to implement into my app but it still crashes. My attempt is provided below. My original code which works is in the paragraph above.

ViewController.swift

import UIKit
import CoreData

class ViewController: UITableViewController {

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

    private let persistentContainer = NSPersistentContainer(name: "AlarmItems")

    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<AlarmItems> = {
        //create fetch request
        let fetchRequest: NSFetchRequest<AlarmItems> = AlarmItems.fetchRequest()

        //configure fetch request
        //fetchRequest.sortDescriptors = [NSSortDescriptor(key: )]

        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)

        fetchedResultsController.delegate = self as? NSFetchedResultsControllerDelegate

        return fetchedResultsController
    }()


    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)
            try fetchedResultsController.performFetch()
        } 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()
            //NSFetchedResultsController.append(item)
            tableView.reloadData()

        } 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 numberOfSections(in tableView: UITableView) -> Int {
        return fetchedResultsController.sections?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        //return alarmItems.count
        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)
        cell.textLabel?.text = alarmItem.value(forKeyPath: "alarmAttributes") as? String
        return cell
    }


}
  • The error is:

    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An instance of NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext'

They also say that it failed to load the model named AlarmItems even though I already have the fetch request function in my code.


Solution

  • First of all delete let persistentContainer = NSPersistentContainer(name: "AlarmItems") and get the context from AppDelegate

    The error is incomplete

    An instance of NSFetchedResultsController requires a non-nil fetchRequest with sort descriptors and managedObjectContext

    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<AlarmItems> = {
        //create fetch request
        let fetchRequest: NSFetchRequest<AlarmItems> = AlarmItems.fetchRequest()
    
        //configure fetch request
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "alarmAttributes", ascending: true)]
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
    
        fetchedResultsController.delegate = self
    
        return fetchedResultsController
    }()
    

    And as already mentioned please name the NSManagedObject subclass in singular form AlarmItem