Michael McKenna
Michael McKenna

Reputation: 794

NSFetchedResultsController is deleting rows instead of updating them after Core Data is being updated

While using the NSFetchedResultsController in my table view, the data is being displayed properly right after creating a new NSManagedObject, but all of the rows/sections are being deleted after updating it. Below, when the update() method is called and the contact is saved, my print statements are outputting that the rows and sections are being deleted from the table view. When the create() method is called, the rows/sections are being inserted (as expected).

Here are the two different sets of output after running:

enter image description here enter image description here

After information is retrieved from an API request, I am updating the corresponding core data model as shown below if the item already exists (as designated by a unique id):

func update(oldContact: NSManagedObject) -> Bool {

    //updates contact
    let contact = populateObject(oldContact)

    // Delete existing phones
    if let phoneDataSet = contact.valueForKeyPath("phones") {
        let phonesArray = phoneDataSet.allObjects as! [NSManagedObject]
        for object in phonesArray {
            context.deleteObject(object)
        }
    }
    // Add phones from response
    for phone in phones {
        phone.createForContact(contact)
    }

    // Save contact
    do {
        try contact.managedObjectContext?.save()
        print("saving contact")
        return true
    } catch {
        let nserror = error as NSError
        print("error upong saving")
        NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
        abort()
    }

    return false
}

  func populateObject(object: NSManagedObject) -> NSManagedObject {

    object.setValue(self.name, forKey: "name")
    object.setValue(self.id, forKey: "id")
    object.setValue(self.firstLetter, forKey: "firstLetter")

    return object
  }

If the item does not exist already in core data, it is created like so:

func create() -> Bool {

    // Quit if contact already exists.
    let data = CoreData().searchObject("Contact", predicate: "id", value: String(self.id))
    guard data == nil else { fatalError("Attempted to insert duplicate contact") }

    //creates a new contact NSManagedObject
    var newContact = NSEntityDescription.insertNewObjectForEntityForName("Contact", inManagedObjectContext: context)
    //sets the contact values
    newContact = populateObject(newContact)

    //creates Phone NSManagedObject, then makes a relationship with contact
    for phone in self.phones {
        phone.createForContact(newContact)
    }

    do {
        //saves the contact object; also saves the relationship with the phone number
        try newContact.managedObjectContext?.save()
        print("Creating contact")
        return true;
    } catch {
        let nserror = error as NSError
        NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
        abort()
    }

    return false
}

My FetchedResultsController delegate methods are as shown:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch (type) {
    case .Update:
        if let indexPath = indexPath {
            if let cell = tableView.cellForRowAtIndexPath(indexPath) as? PeopleAndPlaceCell {
                configureCell(cell, atIndexPath: indexPath)
                tableView.reloadRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
                print("UPDATING ROW")
            }
        }
        break;
    case .Insert:
        if let indexPath = newIndexPath {
            tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            print("INSERTING ROW")
        }
        break;
    case .Delete:
        if let indexPath = indexPath {
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            print("DELETING ROW")
        }
        break;
    case .Move:
        if let indexPath = indexPath {
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        }

        if let newIndexPath = newIndexPath {
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        }
        break;
    }
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    switch type {
    case .Update:
        print("UPDATE SECTION")
        break
    case .Insert:
        self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        print("INSERTING SECTION")
    case .Delete:
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        print("DELETING SECTION")
    case .Move:
        break
    }
}

UPDATE:

The relationship for Contact to Phone is One to Many, and from Phone to Contact is a To-one. The delete rule for the contact relationship in the Phone entity is Cascade. It is also Cascade for the phone relationship in the Contact entity.

Upvotes: 2

Views: 588

Answers (1)

pbasdf
pbasdf

Reputation: 21536

The problem is a result of the "Cascade" delete rule for the contact relationship of the Phone entity. In your update() method, you delete all the "old" Phone objects for the given Contact. The cascade delete rule causes the Contact to be deleted as well. Change this delete rule to "Nullify". (You can leave the inverse relationship, phones, in the Contact entity, as "Cascade": when you delete a Contact, it will delete all the associated Phones).

Upvotes: 1

Related Questions