El Tomato
El Tomato

Reputation: 6707

Core Data with NSFetchedResultsController: Updating Data After Changes

I've been playing with Core Data for the past 18 hours or so. I'm fetching data with NSFetchedResultsController and shows data with UITableView. Adding a new record and deleting the selected record aren't my problems.

class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    // MARK: - Instance variables
    private let persistentContainer = NSPersistentContainer(name: "Profiles") // core data model file (.xcdatamodeld)
    var managedObjectContext: NSManagedObjectContext?

    // MARK: - IBOutlets
    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // loading persistentContainer //
        persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
            if let error = error {
                print("Unable to Load Persistent Store")
            } else {
                do {
                    try self.fetchedResultsController.performFetch()
                } catch {
                    let fetchError = error as NSError
                    print("\(fetchError), \(fetchError.localizedDescription)")
                }
            }
        }

        // notifications //
        NotificationCenter.default.addObserver(self, selector: #selector(profileDidUpdate), name: NSNotification.Name(rawValue: "HomeViewControllerPictureDidSelect"), object: nil)
    }

    // MARK: - fetchedResultsController(controller with the entity)
    fileprivate lazy var fetchedResultsController: NSFetchedResultsController<Person> = {
        // Create Fetch Request with Entity
        let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()

        // Configure Fetch Request
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]

        // Create Fetched Results Controller
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)

        // Configure Fetched Results Controller
        fetchedResultsController.delegate = self
        return fetchedResultsController
    }()
    // MARK: - fetchedResultsController


    // MARK: - Notifications
    @objc func profileDidUpdate(notification: NSNotification) {
        let profile = notification.object as! Profile
        let context = persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Person", in: context)
        let newPerson = NSManagedObject(entity: entity!, insertInto: context)
        newPerson.setValue(profile.uuid, forKey: "uuid") // uuid is used to make each record unique
        newPerson.setValue(profile.firstName, forKey: "firstName")
        newPerson.setValue(profile.lastName, forKey: "lastName")
        newPerson.setValue(profile.age, forKey: "age")
        newPerson.setValue(profile.pictData, forKey: "pictData")
        do {
            try context.save()
            print("saved...")
        } catch {
            print("failed saving")
        }
    }
    // MARK: - Notifications
}

extension HomeViewController: NSFetchedResultsControllerDelegate {
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch (type) {
        case .insert:
            if let indexPath = newIndexPath {
                tableView.insertRows(at: [indexPath], with: .fade)
            }
            break;
        case .delete:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            break;
        default:
            print("...")
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    }
}

Shown above, I create a new record from another view controller, which sends an object of a model (Profile) to the current view controller (HomeViewController). I don't have to reload the table thanks to NSFetchedResultsController.

The entity has several attributes (age, firstName, lastName, pictData, uuid). And I want to change the selected record in the list with two attributes: firstName and lastName. The uuid attribute is used to identify a specific record.

class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    @IBAction func editTapped(_ sender: UIButton) {
        guard let indexPath = tableView.indexPathForSelectedRow else {
            return
        }
        let selectedRow = indexPath.row
        if selectedRow >= 0 {
            editRecord(index: selectedRow)
        }
    }

    func editRecord(index: Int) {
        let indexPath = IndexPath(row: index, section: 0)
        let person = fetchedResultsController.object(at: indexPath)
        let uuid = person.uuid!
        let context = self.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
        fetchRequest.predicate = NSPredicate(format: "uuid == %@", uuid)
        do {
            let result = try context.fetch(fetchRequest)
            if (result.count > 0) {
                let managedObject = result[0] as! NSManagedObject
                managedObject.setValue("Donald", forKey: "firstName")
                managedObject.setValue("Washington", forKey: "lastName")
                try context.save()
                print("Changes saved...")
            }
        } catch {
            print("Failed")
        }
    }
}

Now, if I click on the edit button, the app won't update the list immediately. If I restart the app, I see changes. So how can I update the table with NSFetchedResultsController when I make changes to the selected record? Thanks.

Upvotes: 1

Views: 3117

Answers (1)

Sam Fischer
Sam Fischer

Reputation: 1472

Since you're using the NSFetchedResultsControllerDelegate, you need to handle (for your particular use case), the following cases for the NSFetchedResultsChangeType in your didChange method:

  • insert
  • delete
  • update

Your function should look something like this:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
  switch (type) {
  case .insert:
      if let indexPath = newIndexPath {
          tableView.insertRows(at: [indexPath], with: .fade)
      }
      break;
  case .delete:
      if let indexPath = indexPath {
          tableView.deleteRows(at: [indexPath], with: .fade)
      }
      break;
  case .update:
      tableView.reloadRows(at: [indexPath], with: .automatic)
      break;
  default:
      print("...")
  }
}

Upvotes: 3

Related Questions