Limit display results using NSFetchedResultsController and UITableView (Swift)

Now we struggled with this for 2 weeks and did not find adequate training programs, SO answered any of my numerous failed investigations, I felt it was better to ask here and see if you could offer a good answer that could possibly save me and many others - a lot of time and headache!

Many manuals recommend using NSFetchedResultsController to retrieve data from your master data store, especially if you want to display data in a UITableView. However, almost all textbooks suggest that you want to display all the data at once and stop there. As soon as I try to limit the number of cells and implement the "load more" function, for example, everything starts to fall apart at the seams.

The following code (implemented in Swift) retrieves data from the API using AFNetworking and stores it in Core Data. Data is stored in the AF call success block.

All this works, but I can’t find a successful way to limit the number of displayed items / cells and increase them as the user scrolls.

import UIKit import CoreData class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CLLocationManagerDelegate, NSFetchedResultsControllerDelegate{ @IBOutlet var tableView: UITableView! // TableView Properties private let cellIdentifier = "SearchResultCellIdentifier" var refreshController = UIRefreshControl() // persistant data stores and controllers private var managedObjectContext : NSManagedObjectContext? private var displayCount = 5 // Setup the fetch results controller var fetchedResultsController: NSFetchedResultsController { if _fetchedResultsController != nil { return _fetchedResultsController! } let fetchRequest = NSFetchRequest() // Edit the entity name as appropriate. let entity = NSEntityDescription.entityForName("Entity", inManagedObjectContext: self.managedObjectContext!) fetchRequest.entity = entity fetchRequest.fetchBatchSize = 25 fetchRequest.fetchLimit = 25 let sortDescriptor = NSSortDescriptor(key: "name", ascending: false) fetchRequest.sortDescriptors = [sortDescriptor] // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil) aFetchedResultsController.delegate = self _fetchedResultsController = aFetchedResultsController var error: NSError? = nil if !_fetchedResultsController!.performFetch(&error) { println("fetch error: \(error!.localizedDescription)") abort() } return _fetchedResultsController! } var _fetchedResultsController: NSFetchedResultsController? = nil override func viewDidLoad() { super.viewDidLoad() // setup table view delegate and datasource self.tableView.dataSource = self self.tableView.delegate = self // pull-to-refresh setup self.refreshController.addTarget(self, action: "refreshTable:", forControlEvents: UIControlEvents.ValueChanged) self.tableView.addSubview(self.refreshController) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - Table view data source func numberOfSectionsInTableView(tableView: UITableView) -> Int { // Return the number of sections. return 1 } // ask the NSFetchedResultsController for the section func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let info = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo return info.numberOfObjects } // create and configure each 'UITableViewCell' func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier, forIndexPath: indexPath) as SearchResultCell self.configureCell(cell, atIndexPath: indexPath) return cell } // helper method to configure a UITableViewCell ask NSFetchedResultsController for the model func configureCell(cell: SearchResultCell, atIndexPath indexPath: NSIndexPath) { let entity = self.fetchedResultsController.objectAtIndexPath(indexPath) as Entity cell.title.text = entity.name } // MARK: NSFetchedResultsController Delegate functions func controllerWillChangeContent(controller: NSFetchedResultsController) { self.tableView.beginUpdates() } func controllerDidChangeContent(controller: NSFetchedResultsController) { self.tableView.endUpdates() self.tableView.reloadData() self.refreshController.endRefreshing() } /* Delegate method called: - when a new model is created - when an existing model is updated - when an existing model is deleted */ func controller(controller: NSFetchedResultsController, didChangeObject object: AnyObject, atIndexPath indexPath: NSIndexPath, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath) { switch type { case .Insert: self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade) case .Update: let cell = self.tableView.cellForRowAtIndexPath(indexPath) self.configureCell(cell! as SearchResultCell, atIndexPath: indexPath) self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) case .Move: self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade) case .Delete: self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) default: return } } func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { switch type { case .Insert: println("DEBUG: INSERT SECTION") self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) break case .Delete: println("DEBUG: DELETE SECTION") self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) default: return } } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { self.tableView.deselectRowAtIndexPath(indexPath, animated: true) } // MARK: Core Data Stack /** Save results response from server to CoreData entities */ private func saveSearchResultsResponse(response: AnyObject) { self.deleteAllEntities("Entity") println("DEBUG: Saving new objects to model") // Search results object from response if let searchResultsDict = response as? [String: AnyObject] { if let entities = searchResultsDict["entities"] as? [AnyObject] { if let attributes = NSEntityDescription.entityForName("Entity", inManagedObjectContext: self.managedObjectContext!)?.attributesByName { // save entities } } } } // save changes persistent store var error : NSError? if !(self.managedObjectContext!.save(&error)) { println("ERROR: Error saving model: \(error?.localizedDescription)") } } func deleteAllEntities(entityName: String) { var error: NSError? = nil let allEntityFetchRequest = NSFetchRequest(entityName: entityName) if let savedObjects = self.managedObjectContext?.executeFetchRequest(allEntityFetchRequest, error: &error) as? [NSManagedObject] { for object in savedObjects { self.managedObjectContext?.deleteObject(object as NSManagedObject) } // save changes persistent store if !(self.managedObjectContext!.save(&error)) { println("ERROR: Error saving model: \(error?.localizedDescription)") } } else { println("ERROR: Fetch error: \(error!.localizedDescription)") } } 

The methods I've tried include:

  • Installation and Enlargement in NSFetchedResultsController
    • fetchBatchSize
    • fetchLimit

After a long time, playing with them and exploring them, they seem to be an optimization of memory and do not affect what the table data shows. Definitely doesn't seem to be the right way.

  1. Saving the number of elements that I want to display (e.g. 5). Then increasing this by an offset (for example, another 5) each time the user reaches the bottom. Also returns the "count" of the array for numberOfRowsInSection. This results in EXC_BAD_ACCESS crashes and inconsistent Core Data contexts.

Data is still displayed in cellForRowAtIndexPath using:

 func configureCell(cell: SearchResultCell, atIndexPath indexPath: NSIndexPath) { let entity = self.fFetchedResultsController.objectAtIndexPath(indexPath) as Entity ... } 

This may be the right way, but am I implementing it the wrong way?

  1. NSFetchedResultsController is called after the update, and the results are stored in an array (for example, [Entity]). This array is counted for numberOfRowsInSection, and the data in cellForRowAtIndexPath is retrieved from the array element in indexPath.row.

This causes EXC_BAD_ACCESS to crash when the cell is full, but as soon as the application restarts, it displays the data correctly.

I would really like to stick with NSFetchedResultsController because I believe that it has some powerful sort / predicate function that I would like to use in my application, but now I'm at a dead end. Apologize in advance if this was unambiguously reacted elsewhere, but I could not find such and really appreciate any data.

+5
source share

Source: https://habr.com/ru/post/1211383/


All Articles