Darren
Darren

Reputation: 1712

NSFetchedResultsController not updating when saving to core data

Any ideas on the following? Help greatly appreciated:

I have setup an NSFetchedResultsController as follows:

var _fetchedResultsController: NSFetchedResultsController?

var fetchedResultsController: NSFetchedResultsController {

    if self._fetchedResultsController != nil {
        return self._fetchedResultsController!
    }
    let entity = NSEntityDescription.entityForName("Messages", inManagedObjectContext: self.mOC)

    let messageRequest = NSFetchRequest()
    messageRequest.entity = entity
    let messagePredicate = NSPredicate(format: "messageFrom = %@ OR messageTo = %@", profileID, profileID)
    messageRequest.predicate = messagePredicate
    messageRequest.fetchBatchSize = 20
    let sectionSortDescriptor = NSSortDescriptor(key: "messageDate", ascending: true)
    let sortDescriptors = [sectionSortDescriptor]
    messageRequest.sortDescriptors = sortDescriptors

    let frc = NSFetchedResultsController(fetchRequest: messageRequest, managedObjectContext: self.mOC, sectionNameKeyPath: "sectionTitle", cacheName: nil)
    frc.delegate = self
    self._fetchedResultsController = frc
    return self._fetchedResultsController!

}

It's linked by a to-many relationship with a Chats Entity. Chats >> Messages. One chatter (defined by CurrentChatter) has many messages.

When I load the tableview ... the messages show perfectly using the following in my ViewDidLoad():

do {
    try fetchedResultsController.performFetch()
} catch let fetchError as NSError {
    print("I have been unable to fetch things .. : \(fetchError)")
}

When I save a message using the following boilerplate code, nothing seems to be happening with the NSFetchedResultsController. The tableView isn't updating for some reason and I can't seem to work out why. I have the NSFetchedResultsControllerDelegate in place and defined.

if let newMessage = NSEntityDescription.insertNewObjectForEntityForName("Messages", inManagedObjectContext: mOC) as? Messages {
    newMessage.messageDate = NSDate()
    newMessage.messageFrom = myMainUser.mainUserIDfromServer
    newMessage.messageMustBeSent = false
    newMessage.messageReceivedAtServer = true
    newMessage.messageCode = 0
    newMessage.messageType = 100
    newMessage.messageText = "Here is a message I am sending to you!"
    newMessage.messageTo = Int(profileID)!
    newMessage.messageNewMessage = false

    currentChatter.mutableSetValueForKey("messages").addObject(newMessage)          

    do {
        try mOC.save()
    } catch let saveError as NSError {
        print("I have not been able to save the message ...: \(saveError)")
    }
}

I'm calling mOC.save. I've checked that it's saved and that it's saving to the same ManagedObjectContext. When I manually refresh the tableview, the new message shows.

To test this is working I have a simple print statement as follows:

func controllerWillChangeContent(controller: NSFetchedResultsController) {

    print("I will make changes?")

}

Needless to say, this isn't getting called.

Is this because the new message is being added as part of my CurrentChatter. Or should it update each time there is a change to the Messages Entity? Or have I missed something entirely? 3 hours on this and I'm no closer to understanding why it's not working.

Thanks in advance.

UPDATE:

I've added code to sort the table by Date (added for reference):

import Foundation
import CoreData

class Messages: NSManagedObject {

    func sectionTitle() -> String {

        let myDate = NSDateFormatter()
        myDate.dateFormat = "d MMM YYYY"
        return myDate.stringFromDate(self.messageDate)
    }

}

Upvotes: 1

Views: 2913

Answers (3)

kakubei
kakubei

Reputation: 5400

I had a similar issue. In my case, the problem was that I had set the NSFRC delegate on the type variable before instantiating it with a result from NSFetchedResultsController() call. So the delegate was not being set and none of the delegate methods would work.

Silly mistake, I know, but took me a while to find it.

In your case, if you are getting back something for type in didChangeObject then your delegate is working fine.

You said you were getting the wrong values for type. Print the raw value of the type enum to see what they are. Here is a link to Apple's doc on them:

https://developer.apple.com/library/prerelease/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/index.html#//apple_ref/c/tdef/NSFetchedResultsChangeType

Their order in the switch statement shouldn't matter as long as it is extensive (include them all).

Also, make sure that you are not expecting one type and receiving another. For example:

If adding a new entry, you would expect a .Insert correct? But if you are sorting by name in the NSFRC descriptor, you will receive a .Move instead because the new addition could cause a rearrangement of rows.

This also happened to me and took me a while to find.

Hope this helps a bit.

Upvotes: 2

CyberK
CyberK

Reputation: 1578

If you place the update statement BEFORE the .Insert in the didChangeObject (so put it on the first place), does that help? I had the same behavior and this fixed it for me.. At least, on my device. I now hear beta testers experiencing the same issue.. :(

Upvotes: 0

MirekE
MirekE

Reputation: 11555

You set the delegate, but did you implement controller didChangeObject etc.? You need something like this:

    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        tableView.beginUpdates()
    }

    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
        case .Insert:
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
        case .Delete:
            tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
        case .Update
            // update cell at indexPath
        case .Move:
            tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
        }
    }

    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        tableView.endUpdates()
    }

Upvotes: 2

Related Questions