Search code examples
swiftuitableviewcore-dataswift3nsfetchedresultscontroller

My app crashes when i add core data to a table view


I have two table views in one view controller that both hold separate types of data not related to each other. for some reason whenever I add new data to either project the app crashes because it adds the new data to both table views instead of only one. I'm trying to add the data to the correct table view. here is my code.

var tasks: Todo!
var progress: [Millestone] = []
var milestone: Millestone!
var list: [Todo] = []

var taskfetch: NSFetchedResultsController<Todo>!
var progressfetch : NSFetchedResultsController<Millestone>!


    let fetching: NSFetchRequest<Todo> = Todo.fetchRequest()
    let sorting = NSSortDescriptor(key: "dateadded", ascending: true)
   fetching.predicate = NSPredicate(format: "projectname = %@", "\(title! as String)")
    fetching.sortDescriptors = [sorting]
    if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
        let context = appDelegate.persistentContainer.viewContext
        taskfetch = NSFetchedResultsController(fetchRequest: fetching, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
        taskfetch.delegate = self

        do {
            try taskfetch.performFetch()
            if let fetchedObjects = taskfetch.fetchedObjects {
                list = fetchedObjects
            }
        } catch {
            print(error)
        }
    }

    let lining: NSFetchRequest<Millestone> = Millestone.fetchRequest()
    let sorting2 = NSSortDescriptor(key: "dateadded", ascending: true)
    lining.predicate = NSPredicate(format: "projectname = %@", "\(title! as String)")
    lining.sortDescriptors = [sorting2]
    if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
        let context = appDelegate.persistentContainer.viewContext
        progressfetch = NSFetchedResultsController(fetchRequest: lining, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
        progressfetch.delegate = self

        do {
            try progressfetch.performFetch()
            if let fetchedObjects = progressfetch.fetchedObjects {
                progress = fetchedObjects
            }
        } catch {
            print(error)
        }
    }
func getdata() {
    let context = (UIApplication.shared.delegate as! AppDelegate!).persistentContainer.viewContext

    do {
        print("getting")


        let tasking = try context.fetch(Todo.fetchRequest())


        let progressname = try context.fetch(Millestone.fetchRequest())
    } catch{
        print("whoopsie")
    }
}
 let oktaskaction =  UIAlertAction(title: "Add", style: .default, handler: {(action:UIAlertAction!) -> Void in

        if text.textFields?[0].text != nil, text.textFields?[0].text != "" {
           // self.taskTable.beginUpdates()
            // let song = self.songs[indexPath.row]
            //(UIApplication.shared.delegate as! AppDelegate).saveContext()
            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate){
                self.tasks = Todo(context: appDelegate.persistentContainer.viewContext)
                self.tasks.taskname = text.textFields?[0].text
                self.tasks.projectname = self.title
                self.tasks.completed = false
                let formatter = DateFormatter()
                formatter.dateStyle = DateFormatter.Style.medium
                formatter.timeStyle = DateFormatter.Style.none
                self.tasks.dateadded = self.date
                appDelegate.saveContext()
            }else {
                print("nothing there")
                text.textFields?[0].placeholder = "did not enter text"
            }
            self.taskTable.refreshControl?.beginRefreshing()
            self.getdata()
            self.taskTable.reloadData()

        }

    })


    let okAction = UIAlertAction(title: "Add Milestone", style: .default, handler: {(action:UIAlertAction!) -> Void in
        if text2.textFields?[0].text != nil, text2.textFields?[0].text != "", text2.textFields?[1].text != nil {
            print("i'm working on adding the milestone")
            if let appDelegate = (UIApplication.shared.delegate as? AppDelegate){
                self.milestone = Millestone(context: appDelegate.persistentContainer.viewContext)
                self.milestone.progressname = text2.textFields?[0].text
                self.milestone.date = text2.textFields?[1].text
                self.milestone.projectname = self.title
                appDelegate.saveContext()
                    print("adding to graph")
                    self.chartLegend.append(self.milestone.progressname!)
                    self.chartData.append(self.chartData.count + 1)


                print("saved the new milestone")
            }else {
                print("nothing there")
                text.textFields?[0].placeholder = "did not enter text"
            }
            self.milestoneTableView.reloadData()
            self.projectlinechart.reloadData()

        }

    })
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    print("Begining")
    print("\(list.count)")
    print("\(progress.count)")
    taskTable.beginUpdates()
    milestoneTableView.beginUpdates()

}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
    case .insert:

        if let newIndexPath = newIndexPath {
                print("adding")

            taskTable.insertRows(at: [newIndexPath], with: .fade)
            milestoneTableView.insertRows(at: [newIndexPath], with: .fade)

        }
    case .delete:
        if let indexPath = indexPath {
            print("delete")
            taskTable.deleteRows(at: [indexPath], with: .fade)
            milestoneTableView.deleteRows(at: [indexPath], with: .fade)

        }
    case .update:
        if let indexPath = indexPath {
            print("updating")
            taskTable.reloadRows(at: [indexPath], with: .fade)
            milestoneTableView.reloadRows(at: [indexPath], with: .fade)

        }
    default:
        print("doing something else")
        taskTable.reloadData()
        milestoneTableView.reloadData()

    }

    if let fetchedObjects = controller.fetchedObjects {
        projects = fetchedObjects as! [Project]
        list = fetchedObjects as! [Todo]
        progress = fetchedObjects as! [Millestone]
    }
}
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        print("ending")
        print("\(list.count)")
        print("\(progress.count)")
        taskTable.endUpdates()
        milestoneTableView.endUpdates()



    }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if tableView.tag == 1 {
        return list.count
    } else if tableView.tag == 2 {
        return progress.count
    } else {
        return 0
    }
       }


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellidentifier = "taskcell"

    let cell = tableView.dequeueReusableCell(withIdentifier: cellidentifier, for: indexPath) as! TaskTableViewCell
    if tableView.tag == 1 {
        let tasks = list[indexPath.row]//this is where the crash occurs
        cell.taskname.text = tasks.taskname
        cell.taskname.adjustsFontSizeToFitWidth = true
        if tasks.completed == true {
            cell.accessoryType = .checkmark
        }



   }
    } else if tableView.tag == 2 {
        if let appDelegate = (UIApplication.shared.delegate as? AppDelegate){
            let progress2 = progress[indexPath.row]
            if (progress2.progressname != nil), progress2.date != nil{
            cell.progressname.text = "\(progress2.progressname!) on \(progress2.date!)"
            cell.progressname.adjustsFontSizeToFitWidth = true
            self.chartData.append(self.chartData.count + 1)
            chartLegend.insert(cell.progressname.text!, at: indexPath.row)
            } else {
                cell.progressname.text = "No Milestones"
            }

        }

    }


    return cell
}

Solution

  • Get rid of the following variables progress and list. The fetchedResultsController are tracking changes to the objects, so when an object is deleted, or inserted or moved it updates for you. By having the fetched results copied into the arrays you are looking at out of date information after there is a change. Instead look at the values of the fetchedResultsController directly (ie access self.taskfetch.fetchedObjects or use self.taskfetch.object(at:indexPath).

    The reason this caused a crash is because you are updating your tableview based on changes that the fetchedResultsController(s) are informing you about, but not updating the amount of rows in your table because you looking at the old stale data.

    Another problem is that you are updating BOTH tables when either set of data changes. So if something is inserted in one set of data you insert a row incorrectly in the wrong table. In all controller methods first check which controller it is. Something like if controller == taskfetch {