NSPredicate
This is a class from back in the Objective-C days when block which are Objective-C's version of closures were not available. But NSPredicate
still has it's place in iOS with Core Data.
NSPredicate
allows us to define filters that select specific elements of an array using a simple language called "predicate language".
Example Of Filtering
Below we have a pretty standard CoreDataTableViewController
import CoreData
class NotesViewController: CoreDataTableViewController {
// MARK: TableView Data Source
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Get the note
let note = fetchedResultsController?.object(at: indexPath) as! Note
// Get the cell
let cell = tableView.dequeueReusableCell(withIdentifier: "Note", for: indexPath)
// Sync note -> cell
cell.textLabel?.text = note.text
// Return the cell
return cell
}
}
Where it gets interesting is in the prepare(for:sender:)
method in the NotebooksViewController
which is also a subclass of CoreDataTableViewController
First we create a NSFetchRequest
in Mark 1.
Next we specify how we would like the instances of Note
that will be returned by NSFetchRequest
to be sorted in Mark 2 - we go with creation date and by text.
To filter down to only the notes that belong to the notebook we will be segueing to we use NSPredicate
(which can be configures with a micro-language call predicate query language).
In order to create the NSPredicate
we need to find the exact notebook we'll be segueing to so we find the indexPath for the selected row and use it to get the correct notebook in Mark 3.
The easiest way to create an NSPredicate
is with the initializer that takes a format string and an array of arguments that will be inserted into the format string, which we do in Mark 4. When instantiating the NSPredicate
we use a string "notebook = %@"
, the %@
is a placeholder for an object, in our case a Notebook
Then we add the newly created NSPredicate
to our `NSFetchRequest in Mark 5.
After that we create the FetchResultsController
in Mark 6.
And lastly we inject the FetchResultsController
into the notes view controller in Mark 7 so we can display it.
import CoreData
class NotebooksViewController: CoreDataTableViewController {
// MARK: Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Set the title
title = "CoolNotes"
// Get the stack
let delegate = UIApplication.shared.delegate as! AppDelegate
let stack = delegate.stack
// Create a fetch request
let fr = NSFetchRequest<NSFetchRequestResult>(entityName: "Notebook")
fr.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true),
NSSortDescriptor(key: "creationDate", ascending: false)]
// Create the FetchedResultsController
fetchedResultsController = NSFetchedResultsController(fetchRequest: fr, managedObjectContext: stack.context, sectionNameKeyPath: nil, cacheName: nil)
}
// MARK: Actions
@IBAction func addNewNotebook(_ sender: AnyObject) {
// Create a new notebook... and Core Data takes care of the rest!
let nb = Notebook(name: "New Notebook", context: fetchedResultsController!.managedObjectContext)
print("Just created a notebook: \(nb)")
}
// MARK: TableView Data Source
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// This method must be implemented by our subclass. There's no way
// CoreDataTableViewController can know what type of cell we want to
// use.
// Find the right notebook for this indexpath
let nb = fetchedResultsController!.object(at: indexPath) as! Notebook
// Create the cell
let cell = tableView.dequeueReusableCell(withIdentifier: "NotebookCell", for: indexPath)
// Sync notebook -> cell
cell.textLabel?.text = nb.name
cell.detailTextLabel?.text = String(format: "%d notes", nb.notes!.count)
return cell
}
// MARK: Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier! == "displayNote" {
if let notesVC = segue.destination as? NotesViewController {
// Mark 1 - Create Fetch Request
let fr = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
// Mark 2 - Specify how to sort the notebooks
fr.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false),NSSortDescriptor(key: "text", ascending: true)]
// Mark 3
let indexPath = tableView.indexPathForSelectedRow!
let notebook = fetchedResultsController?.object(at: indexPath)
// Mark 4
let pred = NSPredicate(format: "notebook = %@", argumentArray: [notebook!])
// Mark 5 - Add the Predicate to the Fetch Request
fr.predicate = pred
// Mark 6 - Create FetchedResultsController
let fc = NSFetchedResultsController(fetchRequest: fr, managedObjectContext:fetchedResultsController!.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
// Mark 7 - Inject it into the notesVC
notesVC.fetchedResultsController = fc
}
}
}
}