I am trying to start using MVVM with Swift. I've got a UITableViewController using NSFetchedResultsController, and a background process that adds/edits data in the CoreData db.
I don't know where to handle the fetchedResultsControllerDelegate methods. The viewModel or the viewController?
Because the UITableView gets auto-updated when data in CoreData is updated, it seems to me like making a wrapper view model object around each NSManagedObject instance, and binding that to the table view, is not the right way to do this. That way kind of defeats the purpose of using a NSFetchedResultsController.
Here's the simple MVC code I'm using, what's the best way to MVVM-ify this?
// Data model
@objc(Post)
public class Post: NSManagedObject {
@NSManaged public var userName: String?
@NSManaged public var createdAt: Date?
@nonobjc public class func fetchRequest() -> NSFetchRequest<Post> {
return NSFetchRequest<Post>(entityName: "Post")
}
}
// View controller
class ViewController : UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController<Post>
var tableView:UITableView = {
return UITableView()
}()
override func viewDidLoad(){
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
let fetchRequest:NSFetchRequest<Post> = Post.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "foo = %@ and bar = %@", argumentArray: ["baz", "loreum"])
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Post.createdAt), ascending: false)]
self.fetchedResultController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: #keyPath(Post.createdAt),
cacheName: nil)
self.fetchedResultController.delegate = self
do {
try self.fetchedResultController.performFetch()
} catch let error as NSError {
fatalError("Error: \(error.localizedDescription)")
}
}
// MARK - NSFetchedResultsControllerDelegate
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { /** do stuff **/ }
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { /** do stuff **/ }
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex), with: .left)
case .delete:
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?) { /** do stuff **/ }
// MARK - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { /** do stuff **/ }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "my-cell-identifier", for: indexPath) as! CustomUITableViewCell
configureCell(cell, indexPath: indexPath)
return cell
}
// MARK - Cell configuration
func configureCell(_ cell:CustomUITableViewCell, indexPath:IndexPath){
let o = fetchedResultsController.object(at: indexPath)
cell.textLabel?.text = o.userName
// configure cell more here
}
}