Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating fetchedResultsController for predicate set by UISearchBar

Refactored to vanilla UIViewController

Rather than having the UITableView toggle between active and inactive UISearchController, I refactored to a vanilla UIViewController with a UISearchBar at the top and a UITableView directly underneath it.

What I'm trying to do is toggle the fetchedResultsController between having a predicate which is set by the text in the searchBar and one that grabs everything.

I found this answer, which describes a solution to a similar problem in Objective-C and served as the basis upon which I refactored my Swift project:

How to filter NSFetchedResultsController (CoreData) with UISearchDisplayController/UISearchBar https://stackoverflow.com/a/9512891/4475605

My Problem: How do I update my fetchedResultsController for the searchBar text?

The tableView populates correctly, but I can't figure out how to update the fetchedResultsController for my searchPredicate This is what I've tried.

Here's how I'm declaring my NSPredicate & NSFetchedResultsController at the top of the class:

var searchPredicate = NSPredicate()

// Create fetchedResultsController to store search results
lazy var fetchedResultsController: NSFetchedResultsController = {
    let fetchRequest = NSFetchRequest(entityName: "Song")
    let sortDescriptor = NSSortDescriptor(key: "songDescription", ascending: true);
    // ** THESE TWO LINES CAUSE A CRASH, BUT NO COMPILER ERRORS **
    // let predicate = self.searchPredicate
    // fetchRequest.predicate = predicate
    fetchRequest.sortDescriptors = [sortDescriptor]
    fetchRequest.fetchBatchSize = 20

    let frc = NSFetchedResultsController (fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "songDescription", cacheName: nil)

    frc.delegate = self

    return frc
}()

Here are my UISearchBarDelegate methods:

// MARK: - UISearchBarDelegate methods
// called when text changes (including clear)
internal func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
    print("searchText = \(searchBar.text)")
    searchPredicate = NSPredicate(format: "(songDescription contains [cd] %@) || (songStar contains[cd] %@)", searchBar.text!, searchBar.text!)
    // TODO: figure out how to update fetchedResultsController w/ predicate
}

// called when cancel button pressed
internal func searchBarCancelButtonClicked(searchBar: UISearchBar) {
    searchBar.resignFirstResponder()
    searchBar.text = ""
    // TODO: flip back to normal fetchResultsController
}

Here are my NSFetchedResultsControllerDelegate methods

internal func controllerWillChangeContent(controller: NSFetchedResultsController) {
    print("controllerWillChangeContent")
    tableView.beginUpdates()
}

internal func controllerDidChangeContent(controller: NSFetchedResultsController) {
    print("controllerDidChangeContent")
    tableView.reloadData()
    tableView.endUpdates()
}

internal func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type {
    case .Insert:
        tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
    case .Delete:
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
    case .Update:
        tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
    case .Move:
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
        tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
    }
}

Thank you for reading. I welcome your suggestions.

like image 225
Adrian Avatar asked Oct 29 '15 01:10

Adrian


1 Answers

This is not-so-swift-pseudo-code... it most assuredly will not compile with any swift compiler.

Your FRC is not using a cache, so there is no cache to be deleted, and we can just assign the predicate to the existing FRC fetch request.

You may not want to do a complete new fetch on every character change in the search bar, or you may want to only do the search on the first character, and use subsets for subsequent characters.

// called when text changes (including clear)
internal func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
    var predicate:NSPredicate = nil
    if searchBar.text.length != 0 {
        predicate = NSPredicate(format: "(songDescription contains [cd] %@) || (songStar contains[cd] %@)", searchBar.text!, searchBar.text!)
    }
    fetchedResultsController.fetchRequest.predicate = predicate
    fetchedResultsController.performFetch()
    tableView.reloadData()
}

// called when cancel button pressed
internal func searchBarCancelButtonClicked(searchBar: UISearchBar) {
    searchBar.resignFirstResponder()
    searchBar.text = ""
    fetchedResultsController.fetchRequest.predicate = nil
    fetchedResultsController.performFetch()
    tableView.reloadData()
}
like image 188
Jody Hagins Avatar answered Sep 27 '22 18:09

Jody Hagins