Swift UISearchController is connected to the Core Data Project, the application is running, but the search is not updated

I struggled the last few days with creating a filter in a Swift project for a TableViewController that uses Core Data. I finally realized that I needed to use a UISearchController , create an NSPredicate for searchController.searchBar , etc.

I found this post to be EXTREMELY useful , but after modeling my TVC after this project, I found that "all the lights but no one is home." I can search, the predicate is created in the searchBar , add, delete, etc., but the cells are not updated for the search. I'm missing something, but I don’t know what.

Here are the relevant parts of my code.

 class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchControllerDelegate, UISearchResultsUpdating // Properties that get instantiated later var detailViewController: DetailViewController? = nil var addNoteViewController:AddNoteViewController? = nil // I added this var managedObjectContext: NSManagedObjectContext? = nil // Added variable for UISearchController var searchController: UISearchController! var searchPredicate: NSPredicate? // I added this. It optional on and gets set later override func viewDidLoad() { super.viewDidLoad() self.navigationItem.leftBarButtonItem = self.editButtonItem() if let split = self.splitViewController { let controllers = split.viewControllers let context = self.fetchedResultsController.managedObjectContext let entity = self.fetchedResultsController.fetchRequest.entity! self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController } // UISearchController setup searchController = UISearchController(searchResultsController: nil) searchController.dimsBackgroundDuringPresentation = false searchController.searchResultsUpdater = self searchController.searchBar.sizeToFit() self.tableView.tableHeaderView = searchController?.searchBar self.tableView.delegate = self self.definesPresentationContext = true } // MARK: - UISearchResultsUpdating Delegate Method // Called when the search bar text or scope has changed or when the search bar becomes first responder. func updateSearchResultsForSearchController(searchController: UISearchController) { let searchText = self.searchController?.searchBar.text // steve put breakpoint println(searchController.searchBar.text) if let searchText = searchText { searchPredicate = NSPredicate(format: "noteBody contains[c] %@", searchText) self.tableView.reloadData() println(searchPredicate) } } override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { var note: Note if searchPredicate == nil { note = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Note } else { let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() { return self.searchPredicate!.evaluateWithObject($0) } note = filteredObjects![indexPath.row] as! Note } let context = self.fetchedResultsController.managedObjectContext context.deleteObject(note) var error: NSError? = nil if !context.save(&error) { abort() } } } // MARK: - Fetched results controller var fetchedResultsController: NSFetchedResultsController { if _fetchedResultsController != nil { return _fetchedResultsController! } let fetchRequest = NSFetchRequest() // Edit the entity name as appropriate. let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!) fetchRequest.entity = entity // Set the batch size to a suitable number. fetchRequest.fetchBatchSize = 20 // Edit the sort key as appropriate. let sortDescriptor = NSSortDescriptor(key: "noteTitle", ascending: false) let sortDescriptors = [sortDescriptor] 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: "Master") aFetchedResultsController.delegate = self _fetchedResultsController = aFetchedResultsController var error: NSError? = nil if !_fetchedResultsController!.performFetch(&error) { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. println("Unresolved error \(error), \(error?.userInfo)") abort() } return _fetchedResultsController! } var _fetchedResultsController: NSFetchedResultsController? = nil func controllerWillChangeContent(controller: NSFetchedResultsController) { // self.tableView.beginUpdates() // ** original code, change if doesn't work** steve put breakpoint here // ANSWER said this section is redundant, but keeping it b/c it doesn't crash if searchPredicate == nil { tableView.beginUpdates() } else { (searchController.searchResultsUpdater as! MasterViewController).tableView.beginUpdates() } } func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { var tableView = UITableView() if searchPredicate == nil { tableView = self.tableView } else { tableView = (searchController.searchResultsUpdater as! MasterViewController).tableView } switch type { case .Insert: self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) case .Delete: self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) default: return } } 

I think my problem "lives" here in this section. Autocomplete was my friend here, but I don't see the " searchIndex " referenced by autocomplete. I think something is missing, but I'm not sure what and how.

If you did it like that, thanks for reading. Here's the GitHub repository for the branch I'm working on.

  func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { var tableView = UITableView() if self.searchPredicate == nil { tableView = self.tableView } else { tableView = (self.searchController.searchResultsUpdater as! MasterViewController).tableView } switch type { case .Insert: println("*** NSFetchedResultsChangeInsert (object)") tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) case .Delete: println("*** NSFetchedResultsChangeDelete (object)") tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) case .Update: println("*** NSFetchedResultsChangeUpdate (object)") // TODO: need fix this // ORIGINAL CODE // self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code // PROSPECTIVE SOLUTION CODE println("*** NSFetchedResultsChangeUpdate (object)") if searchPredicate == nil { self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) // original code } else { // Should search the do something w/ the UISearchControllerDelegate or UISearchResultsUpdating // Instead of "indexPath", it should be "searchIndexPath"--How? let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell // My cell is a vanilla cell, not a xib let location = controller.objectAtIndexPath(searchIndexPath) as Location // My object is a "Note" cell.configureForLocation(location) // This is from the other guy code, don't think it applicable to me } case .Move: println("*** NSFetchedResultsChangeMove (object)") tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) default: return } } func controllerDidChangeContent(controller: NSFetchedResultsController) { if self.searchPredicate == nil { self.tableView.endUpdates() } else { println("controllerDidChangeContent") (self.searchController.searchResultsUpdater as! MasterViewController).tableView.endUpdates() } } 

Edit: Per @pbasdf, I am adding TableView methods.

 // MARK: - Table View override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return self.fetchedResultsController.sections?.count ?? 0 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if self.searchPredicate == nil { let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo return sectionInfo.numberOfObjects } let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo return sectionInfo.numberOfObjects } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell self.configureCell(cell, atIndexPath: indexPath) return cell } override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { // Return false if you do not want the specified item to be editable. return true } 
+8
swift core-data filtering uisearchcontroller
source share
1 answer

The problems lie in your datasource methods of the TableView table: numberOfSectionsInTableview: tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: You need each of these methods to return different results if searchPredicate is not zero, like your tableView:commitEditingStyle: method. I would make the filteredObjects property of the instance (defined at the beginning of the class) so that all of these methods can access it:

 var filteredObjects : [Note]? = nil 

Now that the search text is changing, you want to rebuild the filtersObjects array. So, in updateSearchResultsForSearchController add a line to recalculate it based on the new predicate:

  if let searchText = searchText { searchPredicate = NSPredicate(format: "noteBody contains[c] %@", searchText) filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() { return self.searchPredicate!.evaluateWithObject($0) } as [Note]? self.tableView.reloadData() println(searchPredicate) } 

I would also recommend (for simplicity) that when the search is activated, the results are displayed in one section (otherwise you will need to determine how many sections the filtered results fall into - perhaps, but tedious):

 override func numberOfSectionsInTableView(tableView: UITableView) -> Int { if searchPredicate == nil { return self.fetchedResultsController.sections?.count ?? 0 } else { return 1 } } 

Further, if searchPredicate is not nil, the number of rows in this section will consist of filtered objects:

 override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if self.searchPredicate == nil { let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo return sectionInfo.numberOfObjects } else { return filteredObjects?.count ?? 0 } } 

Finally, if searchPredicate is not nil, you need to build the cell using filterObjects data, not fetchedResultsController:

 override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell if searchPredicate == nil { self.configureCell(cell, atIndexPath: indexPath) return cell } else { // configure the cell based on filteredObjects data ... return cell } } 

Not sure what tags, etc. you have in your cells, so I leave this to sort this bit.

+8
source share

All Articles