Searching for information in your app
Search for app-specific content and refine search results using predicates and filters.
Overview
Add search capabilities to your app to find relevant information quickly. Instead of building your own custom search tools, index your content using the Core Spotlight APIs and use the framework-provided queries to retrieve information. Indexing your content makes it available to both your app and to the system’s Spotlight search feature.
Use a CSSearchQuery object to execute a search of your app’s indexed content. With this query object, you specify which attributes of an item to match, and the values that constitute a match. For example, you might tell the query to look for media clips with a duration in a specific range. Although you might incorporate input from your app’s UI when building your query string, use this query type to match specific attributes of your indexed content. For information about how to search based solely on input from a person, see Building a search interface for your app.
Select the attributes you want the query to retrieve
When you execute a query, the system doesn’t automatically retrieve every property of your CSSearchableItem objects. Instead, it retrieves only the properties you specifically request. This behavior improves performance by not spending time to retrieve information your app doesn’t need.
Identify the properties you want, and specify the property names as strings when you initialize your query. For example, to fetch the title and display name of each item, include “title” and “displayName” strings in the fetchAttributes property of your context object.
Create a query string for your search
When you execute a query, Core Spotlight evaluates each item in the index against a query string you provide. Construct your query string from one or more predicates, and use each predicate to evaluate an attribute of the indexed item. If the predicates in the overall query string evaluate to true for an item, the query object returns the item in the search results. For each predicate in your query string, use one of the following formats:
attributeName operator value[modifiers]InRange(attributeName, minValue, maxValue)
In both formats, attributeName is a property name from the CSSearchableItemAttributeSet class. For example, to evaluate the title property of the item, use “title” as the attribute name. The InRange predicate determines whether a property with a numeric value is between the specified minimum and maxium values. Other predicates compare the attribute to the value you specify using one of the following operators:
Operator | Definition |
|---|---|
== | Equal. |
!= | Not equal. |
< | Less than. Works only for numeric and date values. |
> | Greater than. Works only for numeric and date values. |
<= | Less than or equal. Works only for numeric and date values. |
>= | Greater than or equal. Works only for numeric and date values. |
When comparing text values, use modifiers to change the matching behavior the query applies to the comparison. Queries support the following modifiers:
Modifier | Behavior |
|---|---|
c | Performs a case-insensitive search. |
d | Performs a search that ignores diacritical marks. |
w | Matches on word boundaries. This modifier treats transitions from lowercase to uppercase as word boundaries. |
t | Performs a search on a tokenized value. For example, a search field can contain tokenized values. |
* | Performs a wildcard search. Match a substring at the beginning, end, or middle. |
\ | Don’t interpret the character that follows. Use this to include special characters. Examples include |
Combine two predicates using either an AND or OR operation, listed in the table below. Use parentheses to prioritize the evaluation of predicates.
Combination operator | Definition |
|---|---|
| AND operator. If both predicates are true, the entire result is true. If one predicate is false, the result is false. |
| OR operator. If either predicate is true, the entire result is true. Otherwise, the result is false. |
The following examples show how operators, modifiers, and parentheses work for query strings. When trying to match a specific word, notice that the w modifier is more precise than a wildcard because of how it matches word boundaries.
Query string | Example matches |
|---|---|
| Matches “Paris” but not “paris” or “I love Paris”. |
| Matches “Paris” and “paris”, but not “I love Paris”. |
| Matches “Paris”, “paris”, “I love Paris”, and “paris-france.jpg”, but not “Comparison”. |
| Matches “MyWindowClass” and “Broken Window”, but not “NSWindow”. |
| Matches “Frédéric”, but not “Frederic”. |
| Matches “Frédéric” and “Frederic”, regardless of case. |
| Matches words that begin with “paris” like “paris” and “parisol” but not “comparison”. |
| Matches words that end with “paris”. |
| Matches words that contain “paris” anywhere in the string, including “paris”, “parisol”, and “comparison”. |
| Matches a value that is exactly equal to “paris”. |
| Matches audio items that Steve authored. |
| Matches any audio or video items that Steve authored. |
Match dates and times in a query string
When an attribute contains a date or time value, there are two ways you can specify the value portion of your predicate:
Specify a floating-point value with the number of seconds relative to January 1, 2001. You can get this value from a CFDate or Date type using the CFDateGetAbsoluteTime(_:) function.
Specify a property of the built-in
$timevariable.
CSSearchQuery provides the $time variable as a convenient way to specify date values in your query strings. When you start a query, the system initializes this variable to the current date and time. Include this variable in the value portion of your predicate to compare a date-based attribute to the date you specify. The following table lists the properties of the variable you can use in your predicates and how the query matches them against attributes.
| Description |
|---|---|
| Matches an attribute set to the current date and time. |
| Matches an attribute set to the current date. |
| Matches an attribute set to yesterday’s date. |
| Matches an attribute set to a date from the week before the current week. |
| Matches an attribute set to a date from this week. |
| Matches an attribute set to a date in the current month. |
| Matches an attribute set to a date in the current year. |
| Adds |
| Adds |
| Adds |
| Adds |
| Adds |
| Matches the date you specify using an Iso 8601 Date And Time Format compliant string. |
The following example shows a query string that includes different types of predicates and modifiers and uses the $time variable. The predicate matches an item if author is either Tim or Steve and the item’s completion date occurred within the past 10 days. For the completion date check, adding a negative number to the $time.today variable creates a date in the past.
(authorNames == “Tim”wc || authorNames == “Steve”wc) &&
InRange(completionDate,$time.today(-10),$time.today)Start the query and process the results
Search queries are one-shot objects. While they run, they deliver matching items asynchronously to the handlers you provide. When there are no more items, the query stops. You can also stop a query early by calling its cancel() method before the system delivers all matching results.
Create a new query using a query string and a CSSearchQueryContext object, which contains the query configuration parameters. Start the query by fetching the results property of the query object and iterating over the results. The property contains an AsyncSequence, and fetching it starts the query and begins the delivery of the results. The following function searches for items with a specific title and processes the results in an asynchronous task:
import CoreSpotlight
class QueryExample {
func executeQuery(withTitle title : String) {
var results = [CSSearchableItem]()
// Fetch the items with a title that starts with the
// specified string. Perform a case-insensitive comparison.
let queryString = "title == '\(title)*'c"
let context = CSSearchQueryContext()
context.fetchAttributes = ["title", "displayName", "keywords", "contentType"]
// Create the query and specify a handler for the results.
let query = CSSearchQuery(queryString: queryString, queryContext: context)
// Process the results asynchronously.
Task {
do {
// Start the query and iterate over the results.
for try await result in query.results {
let attributeSet = result.item.attributeSet
let foundTitle = attributeSet.title
let foundDisplayName = attributeSet.displayName
// Use the results here.
}
} catch {
// Handle any errors.
}
}
}
}If you prefer not to process the results using an AsyncSequence, specify values for the foundItemsHandler and completionHandler properties. When using handler blocks, you’re responsible for calling the start() method to start the query. To deliver results, the query calls your foundItemsHandler block one or more times, delivering new results with each call. The system executes your completion handler block only once after it finishes delivering all the results.
The following code shows a version of the previous method that uses handler blocks to process the results.
import CoreSpotlight
class QueryExample {
private var query : CSSearchQuery? = nil
mutating func executeQueryWithHandlers(withTitle title : String) {
var results = [CSSearchableItem]()
// Fetch the items with a title that starts with the
// specified string. Perform a case-insensitive comparison.
let queryString = "title == '\(title)*'c"
let context = CSSearchQueryContext()
context.fetchAttributes = ["title", "displayName", "keywords", "contentType"]
// Create the query and specify a handler for the results.
self.query = CSSearchQuery(queryString: queryString, queryContext: context)
self.query?.foundItemsHandler = { (items : [CSSearchableItem]) -> Void in
results.append(contentsOf: items)
}
// Display the results when the query finishes.
self.query?.completionHandler = { (error) -> Void in
if error == nil {
for item in results {
let attributeSet = item.attributeSet
let foundTitle = attributeSet.title
let foundDisplayName = attributeSet.displayName
// Use the results here.
}
} else {
// Handle the error.
}
}
// Start the query.
self.query?.start()
}
}