I'd like to delete a UITableViewCell & a CoreData Object from a UITableView with an animation. I wrote this code for doing it:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let mySelectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
//1
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let fetchRequest = NSFetchRequest(entityName:"Person")
//3
mySelectedCell.backgroundColor = green
mySelectedCell.detailTextLabel?.text = "Done"
mySelectedCell.accessoryType = UITableViewCellAccessoryType.Checkmark
let person = people[indexPath.row]
managedContext.deleteObject(person)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
}
I don't know why but the app is crashing if I select a cell, which I want to delete.
2015-03-29 18:00:10.776 MyApp[3001:79507] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.93/UITableView.m:1582
Crashing line: self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
Does someone knows what I've done wrong?
Update:
tableView.beginUpdates()
var numberItems:Int = people.count
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
numberItems -= [indexPath].count // something like [indexPath].count
tableView.endUpdates()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
application.applicationIconBadgeNumber = people.count
return people.count
}
New error:
2015-03-29 19:14:15.972 MyApp[3389:89566] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.93/UITableView.m:1582
in this line of code: tableView.endUpdates()
If the table view displays Core Data objects using a fetched results controller then the only thing you have
to do to delete an object is to delete the object from the managed object context:
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject)
and of course save the context to make the change permanent.
Updating the table view is then done "automatically" by the
fetched results controller delegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController)
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
func controllerDidChangeContent(controller: NSFetchedResultsController)
You'll find a typical implementation of these methods in the
NSFetchedResultsControllerDelegate documentation, if you haven't
implemented them already.
Also you should not have a "copy" of the fetched objects in your
people array. The fetched results controller is directly used
in the table view data source methods, for example:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
If you create a fresh "Master-Detail + Core Data Application" in Xcode
then you'll get all the necessary template code that you need.
You have to put yout method deleteRowsAtIndexPaths inside this two methods in the following way :
var path = [NSIndexPath]()
// This is only a example with the index [0..numberOfItemsToDelete-1]
for (var i = 0; i < numberOfItemsToDelete; i++) {
path.append(NSIndexPath(forRow: i, inSection: 0))
}
tableView.beginUpdates()
self.tableView.deleteRowsAtIndexPaths(path, withRowAnimation: .Right)
self.numberOfItems -= theNumberYouDeletedAbove // something like [indexPath].count
tableView.endUpdates()
And of course you have to update the numberOfRowsInSection method because it will throw a run-time error , you can do something like this :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfItems
}
Where numberOfItems match the size of the array you use to display the items.
I hope this help you.
FIRST, remove this line which try to duplicate and involve managedContext job an cause crash.
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Right)
SECOND, Now you should able to delete without crash and without animation.
THIRD, Let the NSFetchedResultsController delete and trigger animation in its way with "didChange" handler.
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?)
{
print(indexPath)
if type == .delete{
tableView.deleteRows(at: [indexPath!], with: .automatic)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
//0.4 sec delay before refreshing the table
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 0.4) {
self.refresh()
}
}
func refresh(){
tableView.reloadData()
}
If you can not delete at SECOND step;
Check, if you've implemented:
NSFetchedResultsControllerDelegate
in the class definition and set the delegate in the class also e.g.
yourResultsController.delegate = self
Related
I'm getting the following error when I delete a cell from the table view:
Invalid update: invalid number of sections. The number of sections
contained in the table view after the update (0) must be equal to the
number of sections contained in the table view before the update (1),
plus or minus the number of sections inserted or deleted (0 inserted,
0 deleted)
I'm using the NSFetchedResultsControllerDelegate to respond to the changes in the number of section. I'm retrieving the data from a plist file in Core Data and each addition of the table view cell increases the number of sections accordingly, so 3 cells = 3 sections.
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let goal = fetchedResultsController.object(at: indexPath)
cell.textLabel!.text = goal.title
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let goal = fetchedResultsController.object(at: indexPath)
self.context.delete(goal)
self.saveContext()
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
default:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
let section = IndexSet(integer: sectionIndex)
switch type {
case .delete:
tableView.deleteSections(section, with: .automatic)
default:
break
}
}
context is this:
var context : NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
How I load the saved Core Data onto FRC:
func loadSavedData() {
if fetchedResultsController == nil {
let request = Goal.createFetchRequest()
let sort = NSSortDescriptor(key: "date", ascending: false)
request.sortDescriptors = [sort]
request.fetchBatchSize = 20
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.context, sectionNameKeyPath: "title", cacheName: nil)
fetchedResultsController.delegate = self
}
fetchedResultsController.fetchRequest.predicate = goalPredicate
do {
try fetchedResultsController.performFetch()
tableView.reloadData()
} catch {
print("Fetch failed")
}
}
try calling beginUpdates and endUpdates in these delegate apis also.
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates
}
if you still face issues then in this delegate function, try to call like this:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.performBatchUpdates {
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.deleteSections(section, with: .automatic)
} completion: { (status) in
}
}
I have a pretty straight forward UITableViewController /NSFetchedResultsController case here. It's from Xcode Master-Detail App sample code, So easy to reproduce.
I have a CoreData Model with 1 Entity with 1 String Attribute. It's displayed in a UITableViewController. I use .subtitle system cell type.
By selecting a row, I simply update the String Attribute.
So my problem is, when I add just enough rows for the tableview to scroll (10-11 rows on a iPhone 5s with navbar), and I scroll down, and select any row, the tableview bounces up and down.
If there is less rows (less than 10 rows), or more rows (12 rows and more), the behaviour is normal.
So It seems to be at the limit of the scroll view the problem happens.
If I don't use beginUpdates()/endUpdates(), the problem goes away, but I loose their advantages.
Here is a video link of what happens
class TableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var managedObjectContext: NSManagedObjectContext? = nil
#objc func insertNewObject(_ sender: Any) {
let context = self.fetchedResultsController.managedObjectContext
let newEvent = Event(context: context)
newEvent.aString = "a String"
try? context.save()
}
override func viewDidLoad() {
super.viewDidLoad()
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
}
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let event = fetchedResultsController.object(at: indexPath)
configureCell(cell, withEvent: event)
return cell
}
func configureCell(_ cell: UITableViewCell, withEvent event: Event) {
cell.textLabel?.text = event.aString
cell.detailTextLabel?.text = event.aString
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let event: Event = self.fetchedResultsController.object(at: indexPath)
event.aString = event.aString! + ""
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = fetchedResultsController.managedObjectContext
context.delete(fetchedResultsController.object(at: indexPath))
try? context.save()
}
}
var fetchedResultsController: NSFetchedResultsController<Event> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(keyPath: \Event.aString, ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
try? _fetchedResultsController!.performFetch()
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController<Event>? = nil
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
default:
return
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .update:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
case .move:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)
tableView.moveRow(at: indexPath!, to: newIndexPath!)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
I was running into the same problem, and I found that implementing the estimatedHeightFor... methods fixed the issue for me. The issue seems to stem from using automatic cell heights in the table instead of explicitly defined heights per row.
The table I'm using has both rows and section header/footers, so I needed to define estimated heights for both rows and headers, and this solved the strange bouncing animation during batch updates.
Note that returning 0 for section header heights will use the table view's default value which is likely UITableViewAutomaticDimension, so return a positive number for the estimated heights.
My code in Obj-C:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44; // anything except 0 or UITableViewAutomaticDimension
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section{
return 18; // anything except 0 or UITableViewAutomaticDimension
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section{
return 18; // anything except 0 or UITableViewAutomaticDimension
}
Need your help guys !
I'm going mad on a bug on iOS 9 only.
As you can see below, it is going well on iOS 11
At the beginning, i've got 4 cells with a status "Nouveau" for new.
When I click on a "new" cell, the status change to "En attente" (for "waiting").
On iOS 9, everything is fine until the last cell. When I tap on the last "new" cell, it doesn't move to "waiting" and the "new" section is still there. After this, the table view is not getting updates anymore, it is going back to normal when i change to another viewController and go back to the one containing the table view.
I'm using NSFetchedResultsController. Data is good, good number of sections, good number of elements by section, good title.
At the start 1 sections with title "Nouveau" with 4 objects
At the end, 1 sections with title "En attente" with 4 objects
extension CRDashboardOrdersViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
print( self.fetchResult?.sections?.count ?? 0 )
return self.fetchResult?.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print( self.fetchResult?.sections?.get(index: section)?.numberOfObjects ?? 0 )
return self.fetchResult?.sections?.get(index: section)?.numberOfObjects ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CRDashboardOrderTableViewCell = tableView.dequeueReusableCell(withIdentifier: "CRDashboardOrderTableViewCell") as! CRDashboardOrderTableViewCell
let order: Order = self.fetchResult.object(at: indexPath) as! Order
cell.order = order
if let selectedIndexPath = self.selectedIndexPath, selectedIndexPath == indexPath {
self.tableView.selectRow(at: selectedIndexPath, animated: false, scrollPosition: .none)
}
return cell
}
}
extension CRDashboardOrdersViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndexPath = indexPath
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
print( self.fetchResult?.sections?.get(index: section)?.name ?? "???" )
return self.fetchResult?.sections?.get(index: section)?.name ?? "???"
}
}
extension CRDashboardOrdersViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
print(sectionIndex)
print(sectionInfo.indexTitle)
switch type {
case .insert:
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
default:
break
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
self.tableView.reloadRows(at: [indexPath!], with: .fade)
if indexPath == self.selectedIndexPath {
self.selectedIndexPath = indexPath
}
break
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .fade)
self.selectedIndexPath = nil
break
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .fade)
self.selectedIndexPath = nil
break
case .move:
self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
if indexPath == self.selectedIndexPath {
self.selectedIndexPath = newIndexPath
}
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}
}
You have made a mistake with your cellForRow: and your FetchedResults delegate.
When using a FetchedResultsController, on .update you should not use reloadRows, this causes problems, instead you should have a method which updates a cells model like so:
func updateCell(atPath indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? MyCell {
updateCell(withCell: cell, atIndexPath: indexPath)
}
}
func updateCell(withCell cell: MyCell, atIndexPath indexPath: IndexPath) {
if let MyModel = fetchedResultsController?.object(at: indexPath) {
cell.model = myModel
}
}
Then in your delegate .update you call updateCell(AtIndexPath), to update the cells UI.
You should also use this cell configure method in your cellForRow
I am using UITableView with estimatedRowHeight and UITableViewAutomaticDimension. Also I am using NSFetchedResultsControllerDelegate to reflect the changes.
Everything is work fine. Now the problem is when ever I add some record to CoreData, NSFetchedResultsController called the it's delegate but an unexpected things happen. TableView suddenly scroll to Top every time.
NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .None)
break
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
default: break;
}
}
By googling I found few answers where people suggested to use tableView: heightForRowAtIndexPath: but as my cell height is dynamic. So what should I do?
This is default behaviour of tableview, inserting an row in tableview scroll it to top.
I did faced the same issue with my app.
Here is how i able to manage the content offset of tableview, follow the steps,
1)Store the content offset of UITableview before inserting row or section.
2)Insert objects in array.
3)Reload the tableview
4)Subtract the difference and set the content offset manually.
let oldOffset: CGFloat = tblTest.contentSize.height
tblTest.reloadData()
let newOffset: CGFloat = tblTest.contentSize.height
tblTest.setContentOffset(CGPointMake(0, (newOffset - oldOffset)), animated: false)
This is how i have done in my applications so far, and with dynamic cell to overcome the reinitialise cell everytime, it is working just awesome.
Adding to #jayesh-miruliya 's answer, in your NSFetchedResultsControllerDelegate after the switch statement, put this in your code:
tableView.reloadData()
dispatch_async(dispatch_get_main_queue(), {
let topCellIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self. tableView.scrollToRowAtIndexPath(topCellIndexPath, atScrollPosition: .Top, animated: true)
})
To solve my problem I save the cell height in the willDisplay method and in estimatedHeightForRowAt I'm retrieving the value.
var cellHeights = NSMutableDictionary()
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cellHeights.object(forKey: indexPath) {
return height as! CGFloat
}
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights.setObject(cell.frame.size.height, forKey: indexPath as NSCopying)
}
tableView.reloadData()
if tableView.numberOfRowsInSection(0) > 0
{
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self. tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .None)
break
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
default: break;
}
tableView.reloadData()
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self. tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
}
let cou : Int = self.Array.count as Int
let lastindex : NSIndexPath = NSIndexPath(forRow: cou-1, inSection: 0)
self.TableName.scrollToRowAtIndexPath(lastindex, atScrollPosition: UITableViewScrollPosition.None, animated: true)
This code help you to scroll your tableview at particular position.
func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
tabelView.reloadData()
}
this is work for me
I am tryin to use NSFetchedResultsController in a UITableView.. But for some reason the NSFetchedResultsControllerDelegate are not getting fired when I add a new entry. Here is the complete code
import UIKit
import CoreData
let coreDataStack = CoreDataStack()
class CDListViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fRequest = fetchRequest()
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fRequest, managedObjectContext: coreDataStack.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
}
println(error)
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Menu")
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 2
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultsController.delegate = self
print(fetchedResultsController)
// fetchedResultsController?.performFetch(nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.Delete
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let menus = fetchedResultsController.fetchedObjects as [Menu]
return menus.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let curr = fetchedResultsController.objectAtIndexPath(indexPath) as Menu
// Configure the cell...
cell.textLabel?.text = curr.text
return cell
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
func controllerWillChangeContent(controller: NSFetchedResultsController) {
println("Coming in here")
self.tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let entry = self.fetchedResultsController.objectAtIndexPath(indexPath) as Menu
coreDataStack.managedObjectContext?.deleteObject(entry)
coreDataStack.saveContext()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch(type) {
case NSFetchedResultsChangeType.Insert : self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Delete : self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Update : self.tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
default:
println("Nothing")
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch(type) {
case NSFetchedResultsChangeType.Insert : self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
case NSFetchedResultsChangeType.Delete : self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
break
default:
println("Nothing")
}
}
}
When I add an entry to the context and save the content neither controllerWillChangeContent nor controllerDidChangeContent is being called. I made sure that the context is saved because if I restart the app I can see the recently added entry.
Can anyone find any issue with the code written above?
I noticed you set the delegate twice. Once inside the computed property and once in the viewDidLoad. I think your issue may arise from the fact that you set it for aFetchedResultsController in the computed property first, and thus, the delegate methods are called when aFetchedResultsController's delegate methods are called (which they are not). Deleting the delegation assignment (aFetchedResultsController.delegate = self) in your computed property should resolve this issue.