NSPredicate 💬

Examples of NSPredicate usage


Created by Fluffy.es (Axel) Hosted on GitHub Pages

NSPredicate

This page contains usage examples of NSPredicate, check here for Core Data usage examples 🔋

Get Demo CoreData / NSPredicate Xcode Project

Table of Contents

Basic

Predicate Format and Arguments

String Format Specifier

Basic Comparison

Compound Comparison

Case insensitive Comparison

Techniques

Reuse NSPredicate with substitution variable

Using NSPredicate to filter Array of objects

Examples (Entity’s property…)

Is included in an Array of values

Is not included in an Array of values

Begins with certain string

Contains certain string

Ends with certain string

Wildcard match with string

Regular Expression match with string

Basics

Predicate Format and Arguments

Say for a predicate which select Person that have a name “Asriel” and 50 money :

let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name == %@ AND money == %i", "Asriel", 50)

The format is "name == %@ AND money == %i".

%@ and %i are the format specifiers, %@ will be substituted with an object (eg: String, date etc), whereas %i will be substituted with an integer.

The substitution happens as illustrated below, following the order from left to right :

predicateFormat

%@ (object format specifier) will be replaced with “Asriel” and %i (integer format specifier) will be replaced with 50. Asriel and 50 is the arguments.

After substitution, the predicate will become "name == 'Asriel' AND money = 50" , meaning the NSPredicate will find for Person that have name Asriel and 50 money.


But why can’t I just use “name == ‘Asriel’ AND money = 50” instead of having to use the format specifier thingy?

Yes of course you can! For simple filter which require a hardcoded value I recommend using it. The format specifier substitution is for variable value usually, like this :

// user input a name into textfield
var name = nameTextField.text!

// filter based on the name user has inputed
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "name == %@", name)

String Format Specifier

You can check the full list in Apple official documentation. %@ is used for objects like String, Date, Array etc.

%K is used for Keypath (the property of the entity).

let integerPredicate = NSPredicate(format: "money == %i", 10000)
let doublePredicate = NSPredicate(format: "perimeter > %f", 3.14159)
let stringPredicate = NSPredicate(format: "name == %@", "Asriel")

// eg: find loans that are overdue
let datePredicate = NSPredicate(format: "due_date < %@", Date())

// the above can be replaced with this
let keyPathDatePredicate = NSPredicate(format: "%K < %@", "due_date", Date())

Basic Comparison

Basic comparison symbol like ==, > , < etc.

let equalPredicate = NSPredicate(format: "name == %@", "Steve Jobs")
let notEqualPredicate = NSPredicate(format: "name != %@", "Steve Jobs")

let greaterPredicate = NSPredicate(format: "money > %i", 10000)
let greaterOrEqualPredicate = NSPredicate(format: "money >= %i", 10000)

let lesserPredicate = NSPredicate(format: "money < %i", 10000)
let lesserOrEqualPredicate = NSPredicate(format: "money <= %i", 10000)

Compound Comparison

Join two or more condition together with OR , AND.

// Retrieve records where all conditions are met
let andPredicate = NSPredicate(format: "name == %@ AND money >= %i", "Steve Jobs", 10000)

// Retrieve records as long as one of the condition is met
let orPredicate = NSPredicate(format: "name == %@ OR money >= %i", "Steve Jobs", 10000)

Case insensitive Comparison

For case insensitive comparison, put [c] after the comparison symbol.

// Works for "jobs", "Jobs", "jObS"
let caseInsensitivePredicate = NSPredicate(format: "name ==[c] %@", "Jobs")

Techniques

Reuse NSPredicate with substitution variable

As it is relatively time consuming for the app to parse the format string of the NSPredicate, we should try to reduce creating multiple NSPredicate and reuse similar NSPredicate as much as possible. We can use variable value in NSPredicate denoted by $ sign, and substitute its value by calling withSubstitutionVariables method.

// Persons' name : ["Asriel", "Asgore", "Toriel", "Frisk", "Flowey"]
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")

let reusablePredicate = NSPredicate(format: "name BEGINSWITH $startingName")

// replace $startingName with 'As'
fetchRequest.predicate = reusablePredicate.withSubstitutionVariables(["startingName" : "As"])

do {
  people = try context.fetch(fetchRequest)
  // ["Asriel", "Asgore"]
} catch let error as NSError {
  print("Could not fetch. \(error), \(error.userInfo)")
}

// reuse the predicate with a different starting name
// replace $startingName with 'F'
fetchRequest.predicate = reusablePredicate.withSubstitutionVariables(["startingName" : "F"])

do {
  people += try context.fetch(fetchRequest)
  // ["Asriel", "Asgore", "Flowey", "Frisk"]
} catch let error as NSError {
  print("Could not fetch. \(error), \(error.userInfo)")
}

You can use multiple variables like name BEGINSWITH $startingName AND money > $amount , then call withSubstitutionVariables(["startingName" : "As", "amount": 50]).

Using NSPredicate to filter Array of objects

Other than Core Data, we can also use NSPredicate to filter array of objects. The SELF in the format string means each individual element in the array.

let names = ["Kim Kardashian", "Kim Jong Un", "Jimmy Kimmel", "Ken"]

var filteredNames : [String] = []

// SELF means each element in the array
let containPredicate = NSPredicate(format: "SELF CONTAINS %@", "Kim")

filteredNames = names.filter({ name in
    // the evaluate function will return true if the element satisfy the predicate, 
    // otherwise false
	containPredicate.evaluate(with: name)
})

print("\(filteredNames)")
// ["Kim Kardashian", "Kim Jong Un", "Jimmy Kimmel"]

Examples

Is included in an Array of values

let wantedItemIDs = [1, 2, 3, 5, 8, 13, 21]

// Retrieve record with item_id which is inside the wantedItemIDs array
let inclusivePredicate = NSPredicate(format: "item_id IN %@", wantedItemIDs)

Is not included in an Array of values

let unwantedItemIDs = [1, 2, 3, 5, 8, 13, 21]

// Retrieve record with item_id which is not inside the unwantedItemIDs array
let exclusivePredicate = NSPredicate(format: "NOT (item_id IN %@)", unwantedItemIDs)

Begins with certain string

// Works for "Kim Jong Un", "Kim Kardashian"
let beginPredicate = NSPredicate(format: "name BEGINSWITH %@", "Kim")

// Works for "macintosh", "Macintosh"
let beginCaseInsensitivePredicate = NSPredicate(format: "name BEGINSWITH[c] %@", "mac")
// the [c] means case insensitive match

Contains certain string

// Works for "Steven Paul Jobs", "Logan Paul"
let containPredicate = NSPredicate(format: "name CONTAINS %@", "Paul")

// Works for "Shop1", "shopping", "my shop", "bishop"
let containCaseInsensitivePredicate = NSPredicate(format: "name CONTAINS[c] %@", "shop")
// the [c] means case insensitive match

Ends with certain string

// Works for "Steve Jobs", "Lisa Jobs"
let endPredicate = NSPredicate(format: "name ENDSWITH %@", "Jobs")

// Works for "mundane jobs", "Steve Jobs"
let endCaseInsensitivePredicate = NSPredicate(format: "name ENDSWITH[c] %@", "jobs")
// the [c] means case insensitive match

Wildcard match with string

LIKE is used for wildcard match.

Wildcard match is used to match certain pattern of string, eg: to match img1.png , img10.png andimg100.png, we can use filename LIKE 'img*.png'. The * means zero or more characters between img and .png is accepted.

let filenameArr = ["img.png", "img1.png", "img2.png", "img10.png", "img100.png", "img200.txt", "img300.csv"]

let pngPredicate = NSPredicate(format: "SELF LIKE %@", "img*.png")

let imageArr = filenameArr.filter(){ filename in
	pngPredicate.evaluate(with: filename)
}
print(imageArr)
// ["img.png", "img1.png", "img2.png", "img10.png", "img100.png"]

To match exactly one character, we can use ? , eg: filename LIKE 'img?.png' will match img1.png but not img10.png as it only take in one character between img and .png.

let filenameArr = ["img.png", "img1.png", "img2.png", "img10.png", "img100.png", "img200.txt", "img300.csv"]

let singleCharPngPredicate = NSPredicate(format: "SELF LIKE %@", "img?.png")

let imageArr2 = filenameArr.filter(){ filename in
	singleCharPngPredicate.evaluate(with: filename)
}
print(imageArr2)
// ["img1.png", "img2.png"]

Regular Expression match with string

MATCHES is used for regular expression match.

Regular expression is used for complex string pattern matching. Swift uses ICU regular expression format. For learning regular expression, I recommend this tutorial.

Eg: filename MATCHES 'img\\d{1,3}\\.png' will match filename with 1-3 digits between img and .png like img1.png, img10.png and img100.png but not img1000.png . Double backslash is used to escape the backslash character \ .

let filenameArr = ["img.png", "img1.png", "imgABC.png", "img10.png", "img100.png", "img9000.png", "img12345.png"]

// matches filename that has 1-3 digits between 'img' and '.png'
let regexPredicate = NSPredicate(format: "SELF MATCHES %@", "img\\d{1,3}\\.png")

let filteredArr = filenameArr.filter(){ filename in
    regexPredicate.evaluate(with: filename)
}
print(filteredArr)
// ["img1.png", "img10.png", "img100.png"]

Get Demo Xcode Project

Get demo Xcode project with Core Data / NSPredicate sample code

Try out NSPredicate / Core Data yourself!





+ Weekly-ish iOS Development tips to help you become a better iOS developer.
No Spam. Unsubscribe any time.


Found some mistake or want some example to be added? Create an issue here