Ezra Free
Ezra Free

Reputation: 754

Deleting causes app to crash

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

Answers (3)

Marcus Adams
Marcus Adams

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

andrejs
andrejs

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

Glorfindel
Glorfindel

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

Related Questions