Reputation: 754
I am trying to put together my first iOS app (I'm primarily a PHP dev), and I am running into an issue. I was trying to follow a tutorial to make a simple task manager app, and I have everything working except the delete functionality.
As soon as I try to delete an item, the app crashes with the following error:
2015-06-10 08:33:32.532 Tasks[56594:1355112] *** Assertion failure in
-[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3347.44/UITableView.m:1623
2015-06-10 08:33:32.538 Tasks[56594:1355112] *** Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in
section 0. The number of rows contained in an existing section after the update (1) must
be equal to the number of rows contained in that section before the update (1), plus or
minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and
plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
There's a throw call stack message afterwards that I can post too, if need be.
Here's my MasterViewController.swift
document:
//
// MasterViewController.swift
// Tasks
//
// Created by John Doe on 6/9/15.
// Copyright (c) 2015 John Doe. All rights reserved.
//
import UIKit
class MasterViewController: UITableViewController {
var objects = [Task]()
var count: Int {
return objects.count
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated);
self.objects = TaskStore.sharedInstance.tasks;
self.tableView.reloadData();
}
override func didReceiveMemoryWarning() {
// Dispose of any resources that can be recreated.
super.didReceiveMemoryWarning()
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let task = TaskStore.sharedInstance.get(indexPath.row)
(segue.destinationViewController as! DetailViewController).detailItem = task
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let task = TaskStore.sharedInstance.get(indexPath.row)
cell.textLabel?.text = task.title
cell.detailTextLabel?.text = task.notes
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
TaskStore.sharedInstance.removeTaskAtIndex(indexPath.row)
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
tableView.endUpdates()
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
}
A friend mentioned that I should add the beginUpdates() and endUpdates() methods before and after my code that deletes, but I still get the same error message and crash.
Any advice would be most appreciated, I feel like reading through the reference isn't getting me anywhere. Thanks!
Upvotes: 0
Views: 317
Reputation: 53870
I'm guessing that TaskStore.sharedInstance.tasks
is not a mutable array. When you assign objects to TaskStore.sharedInstance.tasks
, then modify the non-mutable array, you're basically creating a new array and discarding the old one (only it's not discarded because objects
keeps a reference to it), then assigning the new array to TaskStore.sharedInstance.tasks
. Now, TaskStore.sharedInstance.tasks
is different than the array that objects
point to.
Since the count of the table rows and objects.count
differs, you get the error.
You should consider using a mutable array, update objects
after you modify the array, or stop using the variable objects
.
Upvotes: 0
Reputation: 572
After deleting synchronise your objects
array.
self.objects = TaskStore.sharedInstance.tasks
If you want to use objects
array as a source of data, use it everywhere. Or don't use it at all and use TaskStore.sharedInstance
instead.
Upvotes: 0
Reputation: 22651
It has nothing to do with beginUpdates()
and endUpdates()
; those are used if you need multiple animations (insert x rows in section 1, remove y rows in section 2) at once.
Your problem lies in the fact that you're sometimes using the local variable objects
and sometimes the TaskStore.sharedInstance
. You are deleting the task from the TaskStore, but you are still using the objects
to determine the amount of rows.
The easiest way is to get rid of objects
entirely, and only use the TaskStore.sharedInstance
. However, there are certain cases where it makes sense to retain a local copy, e.g. if you include 'Save' and 'Cancel' buttons. In that case, you should fetch the objects in viewDidLoad
(as you do now), use objects
everywhere else, and when the user clicks 'Save', write the changes back to the store.
Upvotes: 1