0

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
}
Lucky-K
  • 47
  • 8
  • Can you share where the app crashes and the error message that you get? – Mukul More Apr 17 '17 at 16:55
  • sure the app crashes at this code let tasks = list[indexPath.row] the error I get is fatal error: NSArray element failed to match the Swift Array – Lucky-K Apr 17 '17 at 17:52
  • Add exception breakpoint and run again. Generally if it's a core data issue it will clearly report what's messed up and stop at the line. Also sharing the console output would be very helpful. – Ben Lu Apr 17 '17 at 17:52
  • ok i added the breakpoint it stops at the tasktable.endupdates code i'll copy and paste the console output – Lucky-K Apr 17 '17 at 17:56
  • i also get this error but it doesn't crash the app An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) – Lucky-K Apr 17 '17 at 17:58
  • CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo – Lucky-K Apr 17 '17 at 17:59
  • Look at the update: case of delegate method. – Mannopson Apr 18 '17 at 01:22
  • i think the problem lies in having two tableviews in one view controller. – Lucky-K Apr 18 '17 at 13:48

1 Answers1

0

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 {

Jon Rose
  • 8,373
  • 1
  • 30
  • 36