Introduction
At WWDC 2015, Apple has officially announced iOS 9. In addition to many new features and improvements, this update also gives developers the opportunity to make the content of their apps available. More detection and access via Spotlight search. The new API available in iOS 9 allows you to index any content or status of the interface in your application, making it easy to reach your users through Spotlight. The three components of the new search APIs are:
-
NSUserActivity
class, which is designed to view the content of the app. - Core Spotlight framework, designed for any content of the app.
- Web markup, designed for apps that have content that is symmetrical on the website.
In this article, we will show you how you can use the NSUserActivity
class and Core Spotlight framework in your application.
Prerequisites
This tutorial requires you to be running Xcode 7 on OS X 10.10 or later. To follow along with us, you can also download the starter project from GitHub .
1. Using NSUserActivity
In the first part of this tutorial, I will show you how you can index the content of an application through NSUserActivity classes. This API is one of the similar ones used for Handoff, a feature introduced in iOS 8 last year, and handles both storing and restoring the current state of the application.
If you haven't worked with NSUserActivity
, the author suggests you read their tutorial here including Handoff and NSUserActivity
before we continue.
Before writing any code, open the starter project and run the app with a virtual machine or a real machine. At this point, you will find that the application simply displays a list of four TV shows and detailed pages for each program.
To start, open the project and open the DetailViewController.swift file. Add the configureView
method of the DetailViewController
class with the following lines of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func configureView () { // Update user interface for the detail item. if self.nameLabel! = nil && self.detailItem! = nil { self.nameLabel.text = detailItem.name self.genreLabel.text = detailItem.genre let dateFormatter = NSDateFormatter () dateFormatter.timeStyle = .ShortStyle self.timeLabel.text = dateFormatter.stringFromDate (detailItem.time) let activity = NSUserActivity (activityType: "com.tutsplus.iOS-9-Search.displayShow") activity.userInfo = ["name": detailItem.name, "genre": detailItem.genre, "time": detailItem.time] activity.title = detailItem.name var keywords = detailItem.name.componentsSeparatedByString ("") keywords.append (detailItem.genre) activity.keywords = Set (keywords) activity.eligibleForHandoff = false activity.eligibleForSearch = true //activity.eligibleForPublicIndexing = true //activity.expirationDate = NSDate () activity.becomeCurrent () } } |
The code to configure the labels in the view controller is unchanged, but take a step-by-step understanding of the code about user activity:
- Create a new
NSUserActivity
object with unique identifier is com.tutsplus.iOS-9-Search.displayShow . The Starter project has been preconfigured to use this identifier to make sure this identifier does not change. - Then assign a value as a dictionary to the
userInfo
attribute of the user activity. This will be used later to restore the state of the app. - Next is to give the
title
attribute of the user activity a string value. This will display in the Spotlight search results bar. - To ensure that the content of the app is more searchable than its title, you also provide a set of keywords for it. In the code above, the set of keywords includes each word of the program name as well as its categories.
- Next, you set some properties of the NSUserActivity object to tell the operating system you want the user activity to be used. In this tutorial, we only focus on the search component of the API so we will disable Handoff and enable search .
- Finally, when you call the
becomeCurrent
method of the user activity in this location it will automatically be added to the device's search result index.
In the code above, you can see two comment lines. While we will not use those properties in this tutorial, it is important to know if there are other attributes that can be used.
- With the above implementation, a user activity and search results are generated for each occurrence only when the application has been opened. When you perform the user activityForPublicIndexing, Apple begins to monitor the use and interaction of these special activities from the user's search results. If the search results are interactive by many users, Apple will bring users to their cloud index . Once user activity is in this cloud index , it can be searched by anyone who has installed your application, regardless of whether they open specific content or not. This attribute should only be set so that operations are accessible by all users of your application.
- A user activity may also have an
expirationDate
option. When this property is set, your user activity will only appear in search results until the specified date.
Now that you know how to create an NSUserActivity
that can display in Spotlight's search results, you're ready to test it. Build and run the app and open some programs in the app. Once you're done, return to the home screen (press Command-Shift-H in iOS Simulator) and drag the screen down or drag to the left to see the search screen.
Start typing a few letters of the program you open in the app and you'll see it displayed in the search results as shown below.
In addition, you can enter 1 category in the programs you have opened. Because the keyword you assign to the user activity, this will also cause the results to be displayed in the search results.
Content in your app is indexed correctly by the system and the results are displayed in Spotlight. However, when you choose a result, your application does not bring users to the corresponding search results. It just runs the app.
Luckily, similar to Handoff , you can use the NSUserActivity
class to restore the correct state in the app. To do this, you need to implement 2 methods.
Execute the application(_:continueUserActivity:restorationHandler:)
method application(_:continueUserActivity:restorationHandler:)
in AppDelegate
as shown below.
1 2 3 4 5 6 |
func application (application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool { let splitController = self.window? .rootViewController as! UISplitViewController let navigationController = splitController.viewControllers.first as! UINavigationController navigationController.topViewController? .restoreUserActivityState (userActivity) return true } |
Next, execute the restoreUserActivityState(_:)
method in MasterViewController
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
override func restoreUserActivityState (activity: NSUserActivity) { if let name = activity.userInfo? ["name"] as? String, let genre = activity.userInfo? ["genre"] as? String, let time = activity.userInfo? ["time"] as? NSDate { let show = Show (name: name, genre: genre, time: time) self.showToRestore = show self.performSegueWithIdentifier ("showDetail", sender: self) } else { let alert = UIAlertController (title: "Error", message: "Error retrieving information from userInfo: n (activity.userInfo)", preferredStyle: .Alert) alert.addAction (UIAlertAction (title: "Dismiss", style: .Cancel, handler: nil)) self.presentViewController (alert, animated: true, completion: nil) } } |
Build and run the app again and try to find a program. When you select a program in the search results, the app will take you straight to the detail view controller and display the current information for the program you selected.
2. Using Framework Core Spotlight
Another set of APIs in iOS 9 that supports user content search in your app is the Core Spotlight framework. This framework has a database design style and lets you provide more information about the content you want it to be searched for.
Before you can use the Core Spotlight framework, we need to connect the project to this framework. In Project Navigator , select the project and open the Build Phases tab at the top. Next, open the Link Binary With Libraries section and click on the + sign. In the menu that appears, find CoreSpotlight and connect the project to this framework. Repeat these steps with MobileCoreServices framework.
Next, to make sure that the search results our application provides are from Core Spotlight, delete the app from your test device or iOS Simulator, and comment the following lines in the DetailViewController
class:
1 |
activity.becomeCurrent () |
Finally, open the MasterViewController.swift file and add the following lines before defining the Show
structure:
1 2 |
import CoreSpotlight import MobileCoreServices |
Next, add the following code to viewDidLoad
of the MasterViewController
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var searchableItems: [CSSearchableItem] = [] for show in objects { let attributeSet = CSSearchableItemAttributeSet (itemContentType: kUTTypeItem as String) attributeSet.title = show.name let dateFormatter = NSDateFormatter () dateFormatter.timeStyle = .ShortStyle attributeSet.contentDescription = show.genre + "n" + dateFormatter.stringFromDate (show.time) var keywords = show.name.componentsSeparatedByString ("") keywords.append (show.genre) attributeSet.keywords = keywords let item = CSSearchableItem (uniqueIdentifier: show.name, domainIdentifier: "tv-shows", attributeSet: attributeSet) searchableItems.append (item) } CSSearchableIndex.defaultSearchableIndex (). IndexSearchableItems (searchableItems) {(error) -> Void print if error! = nil { print (error? .localizedDescription) } else { // Items were indexed successfully } } |
Before verifying this code, analyze each step in the for
loop.
- Create a
CSSearchableItemAttributeSet
object, passing an item to the content type parameter. If the search results link to an image, for example, you will pass thekUTTypeImage
. - You assign the
title
attribute of the attribute set to the value of the show name. LikeNSUserActivity
, this title is what will appear at the top of the search results. - Next, create a description string and assign this value to the
contentDescription
attribute of the set attribute. This string will be displayed below the Spotlight result title. - You initialize an array containing search result keywords like you did with
NSUserActivity
. - Finally, you create a
CSSearchableItem
with a unique item identifier, the unique domain identifier to group items together and a set attribute. UnlikeNSUserActivity
, which returns user activity from the search results, the unique identifiers you use forCSSearchableItem
are the only information you receive from the system when your search results are selected from the user. You need to use these identifiers to restore your app to the most accurate state.
Each time you initialize a CSSearchableItem
for TV programs, you index them using the indexSearchableItems(_:completionHandler:)
in the default CSSearchableIndex
object.
Build and run the app, all programs will be cataloged by Spotlight. Navigate to the search frame and search for a program.
The search results from Core Spotlight are handled by the same methods as NSUserActivity
, but this process is slightly different. When a CSSearchableItem
is selected from the search results, the system will create an NSUserActivity
object for you that contains the unique identifier of the selected item.
In the application(_:continueUserActivity:restorationHandler:)
of appdelegate, you can use the following method to get the information you need from the search results from Core Spotlight:
1 2 3 4 5 6 7 8 |
if userActivity.activityType == CSSearchableItemActionType { if let identifier = userActivity.userInfo? [CSSearchableItemActivityIdentifier] as? String { // Use identifier to display content correct for this search result return true } } |
A good way to index content from your app with the Core Spotlight framework is to delete items when they are no longer needed. Class CSSearchableIndex
provides 3 methods to delete the searched items:
-
deleteAllSearchableItemsWithCompletionHandler(_:)
-
deleteSearchableItemsWithDomainIdentifiers(_:completionHandler:)
-
deleteSearchableItemsWithIdentifiers(_:completionHandler:)
As an example, add the following code to the end of the viewDidLoad
function of the MasterViewController
class:
1 2 3 4 5 6 7 8 |
CSSearchableIndex.defaultSearchableIndex (). DeleteSearchableItemsWithDomainIdentifiers (["tv-shows"]) {(error) -> Void print if error! = nil { print (error? .localizedDescription) } else { // Items deleted successfully } } |
Build and run the app again. When you try to find any program, there is no return result because they have been deleted from the table of contents.
3. Combining NSUserActivity
and Core Spotlight
A new thing added to the NSUserActivity
class in iOS 9 is the contentAttributeSet
attribute. This property allows you to assign a CSSearchableItemAttributeSet
, as well as the one you created earlier. This attribute set allows your search results for the NSUserActivity
object to display the same details as the search results from Core Spotlight.
Start by adding the following import section at the top of the DetailViewController.swift file:
1 2 |
import CoreSpotlight import MobileCoreServices |
Next, update the configureView
function in the DetailViewController
class in the following way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
func configureView () { // Update user interface for the detail item. if self.nameLabel! = nil && self.detailItem! = nil { self.nameLabel.text = detailItem.name self.genreLabel.text = detailItem.genre let dateFormatter = NSDateFormatter () dateFormatter.timeStyle = .ShortStyle self.timeLabel.text = dateFormatter.stringFromDate (detailItem.time) let activity = NSUserActivity (activityType: "com.tutsplus.iOS-9-Search.displayShow") activity.userInfo = ["name": detailItem.name, "genre": detailItem.genre, "time": detailItem.time] activity.title = detailItem.name var keywords = detailItem.name.componentsSeparatedByString ("") keywords.append (detailItem.genre) activity.keywords = Set (keywords) activity.eligibleForHandoff = false activity.eligibleForSearch = true //activity.eligibleForPublicIndexing = true //activity.expirationDate = NSDate () let attributeSet = CSSearchableItemAttributeSet (itemContentType: kUTTypeItem as String) attributeSet.title = detailItem.name attributeSet.contentDescription = detailItem.genre + "n" + dateFormatter.stringFromDate (detailItem.time) activity.becomeCurrent () } } |
Build and run the app, open some programs. When you search for a program, you will see that your results, created with NSUserActivity
, contain the same level of detail as the search results from Core Spotlight.
Epilogue
In this tutorial, you learned how to make an application whose content can be accessed via iOS Spotlight using the NSUserActivity
class and Core Spotlight framework. The author also shows us how to index content from the app using both of these APIs and how to restore the app state when a search result is selected from the user.
The new search APIs introduced with iOS 9 are easy to use and make your application content easier to find and more accessible to your users.
Source IDE Academy via code.tutsplus.com
Source IDE Academy via code.tutsplus.com