I am looking for a decent solution to this problem. I am wanting to implement some simple search functionality on a TableView that I have.
All the examples I have found either use the deprecated UISearchDisplayController
or use the new UISearchController
but without NSFetchedResultsController
Currently this is populated using Core Data
/ NSFetchedResultsController
So far I have managed to get it to a point where I can gather the users' search string (woo!). I am aware that I may need a separate FRC
to perform the search on, but as mentioned above all attempts up to now have failed.
My class is conforming to the following protocols:
class JobListController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate, UISearchBarDelegate{
I can't use UITableViewController
as I have already written loads of existing functionality that relies on this class being a UIViewController
I have my two IBOutlets
:
@IBOutlet var tblJobs : UITableView!
@IBOutlet weak var searchBar: UISearchBar!
and my empty arrays to hold my various Core Data
bits and bobs:
var workItems = [Work]()
var filteredWorkItems = [Work]()
Here is how I am initialising my FRC
, along with my MOC
and I've left in my empty second FRC
as I am quite sure it will be needed at some point:
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
lazy var fetchedResultsController: NSFetchedResultsController = {
let workFetchRequest = NSFetchRequest(entityName: "Work")
let primarySortDescriptor = NSSortDescriptor(key: "createdDate", ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: "town", ascending: true)
workFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
let frc = NSFetchedResultsController(
fetchRequest: workFetchRequest,
managedObjectContext: self.managedObjectContext!,
sectionNameKeyPath: "createdDate",
cacheName: nil)
frc.delegate = self
return frc
}()
var searchResultsController: NSFetchedResultsController?
In my viewDidLoad
function I am setting up the delegates / data source for my table and the searchBar:
tblJobs.delegate = self
tblJobs.dataSource = self
searchBar.delegate = self
and here is the searchBar
function which is where I am up to. The stringMatch
variable is leftover from a previous attempt, I am hoping to be able to search by a multitude of different parameters here, but if I can get just one working it will be a solid start.
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
println("Search text is \(searchText)")
self.filteredWorkItems = self.workItems.filter({( work: Work) -> Bool in
//
let stringMatch = work.postcode.rangeOfString(searchText)
return stringMatch != nil
})
if(filteredWorkItems.count == 0){
searchActive = false;
} else {
searchActive = true;
}
self.tblJobs.reloadData()
}
Here is my cellForRowAtIndexPath
function to show how I am pulling data from the fetchedResultsController
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = self.tblJobs.dequeueReusableCellWithIdentifier(
"JobCell", forIndexPath: indexPath)
as! JobTableViewCell
let workItem = fetchedResultsController.objectAtIndexPath(indexPath) as! Work
//...
return cell
}
So you can see I've got a few things going on here, ultimately I am wanting to figure out how I use my newly gotten searchText
string to query against my FRC
, and then for the results to filter properly in the View.
Update:
I have attempted to add the search string to my NSPredicate
in the FRC
like so:
lazy var fetchedResultsController: NSFetchedResultsController = {
../
workFetchRequest.predicate = NSPredicate(format:"title contains[cd] %@", savedSearchTerm!)
//...
return frc
}()
Which results in 'JobListController.Type' does not have a member named 'savedSearchTerm'
At the top of my class I have set it up like this:
var savedSearchTerm: NSString?
So not sure what I'm doing wrong?
From your code, I assume you want to use the same table view to display the results. So you just need to update your FRC with a new filter based on the search term.
Store the search term in a variable. In the FRC factory function, include the predicate, something like this:
When the text changes, reset the FRC and reload.
If you have additional filters, such as scope buttons, add additional terms to the predicate.
Swift 4.2
This is a working solutions from one of my app. I have trimmed it down to make it simple to show how it works.
I have a database of around 6000 rows in which I search via 3 different scopes: Notes, Author and Keywords. I call
initializeFetchedResultsController
in theviewDidLoad
function with default values. And later when user starts typing in the Search field, start calling it again with the required value.The Fetch part:
The search controller:
And this in my
notesSearched
method which re-initialize the fetch results controller and reload the table every time.While calling doing so many table reloads might not be the most efficient way to do this, but it is lightening fast, and since this updates the table in real-time as user is typing it provides a wonderful user experience.