Reputation: 6707
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
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:
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