I'd like to have a list of presenters (speakers) in a tableView. I'm using the "edit" button in the upper right corner of my tableView to unhide or hide a addSpeakerCell at the end of all objects that are currently being displayed. This works nicely and without problems. I can delete objects and toggle the edit button and everything works as it is supposed to.
But when getting to the addSpeakerView (via the addSpeakerCell) and add a speaker, when hitting the save-button and getting back to the tableView, the following warning appears:
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 (0), plus or minus the number of rows inserted or deleted from that section (0 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)
My guess is the addSpeakerCell is not updated with the new indexPath. But then I still couldn't find out how to solve it...
If someone could kindly point me to the right solution or even provide me with the necessary code-snippet, I would be eternally grateful
My code of the tableView:
SpeakersViewController:
class SpeakersViewController: UITableViewController
{
var managedObjectContext: NSManagedObjectContext!
lazy var fetchedResultsController: NSFetchedResultsController =
{
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Speaker", inManagedObjectContext: self.managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchBatchSize = 20
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext,
sectionNameKeyPath: nil,
cacheName: "Speaker")
fetchedResultsController.delegate = self
return fetchedResultsController
}()
var singleEdit = false // indicates user is swipe-deleting a particular speaker
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = self.editButtonItem()
tableView.allowsSelectionDuringEditing = true
// CoreData Stuff
NSFetchedResultsController.deleteCacheWithName("Speaker")
performFetch()
}
func performFetch()
{
var error: NSError?
if !fetchedResultsController.performFetch(&error)
{
print("An error occurred: \(error?.localizedDescription)")
}
}
deinit
{
fetchedResultsController.delegate = nil
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
// Unhide or hide the AddSpeakerCell
override func setEditing(editing: Bool, animated: Bool)
{
super.setEditing(editing, animated: true)
if !singleEdit // if user is not swipe-deleting Speaker
{
self.navigationItem.setHidesBackButton(editing, animated: true)
}
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
let rows = sectionInfo.numberOfObjects
let indexPath = NSIndexPath(forRow: rows, inSection: 0)
let indexPaths = [indexPath]
if (editing)
{
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
}
else
{
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath.row == sectionInfo.numberOfObjects
{
return .Insert
}
else
{
return .Delete
}
}
// Unhide or hide the AddSpeakerCell
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
var rows = sectionInfo.numberOfObjects
if (editing)
{
rows++
}
return rows
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath.row < sectionInfo.numberOfObjects
{
let cell = tableView.dequeueReusableCellWithIdentifier("SpeakerCell") as SpeakerCell
let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
cell.configureForSpeaker(speaker)
return cell
}
else
{
let cell = tableView.dequeueReusableCellWithIdentifier("AddSpeakerCell") as UITableViewCell
return cell
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
if editingStyle == .Delete
{
let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
managedObjectContext.deleteObject(speaker)
var error: NSError?
if !managedObjectContext.save(&error)
{
print("An error occurred: \(error?.localizedDescription)")
}
}
}
override func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath)
{
singleEdit = true
}
override func tableView(tableView: UITableView, didEndEditingRowAtIndexPath indexPath: NSIndexPath)
{
singleEdit = false
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "AddSpeaker"
{
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddSpeakerViewController
controller.managedObjectContext = managedObjectContext
}
}
}
extension SpeakersViewController: NSFetchedResultsControllerDelegate
{
func controllerWillChangeContent(controller: NSFetchedResultsController)
{
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
{
if indexPath?.section == 0
{
switch type
{
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath?.row < sectionInfo.numberOfObjects
{
let cell = tableView.cellForRowAtIndexPath(indexPath!) as SpeakerCell
let speaker = controller.objectAtIndexPath(indexPath!) as Speaker
cell.configureForSpeaker(speaker)
}
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
{
if sectionIndex == 0
{
switch type
{
case .Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Update:
break
case .Move:
break
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController)
{
tableView.endUpdates()
}
}
AddSpeakerViewController:
class AddSpeakerViewController: UITableViewController
{
var managedObjectContext: NSManagedObjectContext!
@IBOutlet weak var speakerFirstNameTextField: UITextField!
@IBOutlet weak var speakerLastNameTextField: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
@IBAction func cancelTapped(sender: AnyObject)
{
dismissViewControllerAnimated(true, completion: nil)
}
@IBAction func saveTapped(sender: AnyObject)
{
let speaker = NSEntityDescription.insertNewObjectForEntityForName("Speaker", inManagedObjectContext: managedObjectContext) as Speaker
speaker.firstName = speakerFirstNameTextField.text
var error: NSError?
if !managedObjectContext.save(&error)
{
println("Error: \(error)")
abort()
}
dismissViewControllerAnimated(true, completion: nil)
}
}
Well, after some more hours of trying and going and testing every single line, I finally found out what was causing the problem.
Checking in
controller(didChangeObject)
for
indexPath?.section == 0
caused the problem. I don't really know why... But at least it works.