Hot File

Introducing Search APIs in iOS 9

View: 342    Dowload: 0   Comment: 0   Post by: hanhga  
Author: none   Category: iPhone OS   Fields: Other

9 point/2 review File has been tested

This tutorial requires that you are running Xcode 7 on OS X 10.10 or later. To follow along with me, you also need to download the starter project from GitHub.

Introduction

This tutorial requires that you are running Xcode 7 on OS X 10.10 or later. To follow along with me, you also need to download the starter project from GitHub.

In the first part of this tutorial, I'm going to show you how you can index an app's content through the NSUserActivity class. This API is the same one that is used for Handoff, a feature introduced in iOS 8 last year, and handles both saving and restoring an application's current state.

If you have not worked with NSUserActivity before, then I suggest you first read mymy tutorial covering the basics of Handoff and NSUserActivity before continuing with this one.

Before writing any code, open the starter project and run the app in the iOS Simulator or on a test device. At this stage, you will see that the app simply displays a list of four TV shows and a detail page for each one.

Show list
Show detail

To begin, open the starter project and navigate to DetailViewController.swift. Replace the configureView method of the DetailViewController class with the following implementation:

func configureView() {
    // Update the 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 that configures the labels in the view controller is unchanged, but let's go through the user activity code step by step:

  1. You create a new NSUserActivity object with the unique identifiercom.tutsplus.iOS-9-Search.displayShow. The starter project has already been configured to use this identifier so be sure to leave this identifier unchanged.
  2. You then assign a userInfo dictionary to the user activity. This will be used later to restore the state of the application.
  3. You give the activity's title property a string value. This is what will show up in the Spotlight search results.
  4. To ensure that the content is searchable by more than just its title, you also provide a set of keywords. In the above code snippet, the set of keywords includes each word of the show's name as well as its genre.
  5. Next, you set a number of properties of the NSUserActivity object to tell the operating system what you want this user activity to be used for. In this tutorial, we are only looking at the search component of the API so we disable Handoffand enable search.
  6. Finally, you call the becomeCurrent method on the user activity at which point it is automatically added to the device's index of search results.

In the above implementation, you probably noticed the two lines in comments. While we won't be using these properties in this tutorial, it's important to know what each property is used for.

  • With the above implementation, a user activity and search result is created for each individual show only once the application has been opened. When you make your user activity eligibleForPublicIndexing, Apple begins to monitor the usage and interaction of this particular activity from the user's search results. If the search result is engaged by many users, Apple promotes the user activity to its own cloud index. Once the user activity is in this cloud index, it is searchable by anyone who has installed your application, regardless of whether they have opened that particular content or not. This property should only be set to true for activities that are accessible by all users of your application.
  • A user activity can also have an optional expirationDate. When this property is set, your user activity will only show up in search results up until the specified date.

Now that you know how to create an NSUserActivity capable of displaying search results in Spotlight, you are ready to test it out. Build and run your app, and open up a few of the shows in your application. Once you have done this, go back to the home screen (press Command-Shift-H in the iOS Simulator) and swipe down or scroll to the far left screen to bring up the search view.

Start typing the title of one of the shows that you opened and you will see that it shows up in the search results as shown below.

Start typing the title of one of the shows

Alternatively, enter the genre of one of the shows you opened. Because of the keywords that you assigned to the user activity, this will also cause the show to be listed in the search results.

Enter the genre of one of the shows

Your application's content is correctly indexed by the operating system and results are showing up in Spotlight. However, when you tap a search result, your application doesn't take the user to the respective search result. It merely launches the application.

Luckily, as with Handoff, you can use the NSUserActivity class to restore the correct state in your application. To make this work we need to implement two methods.

Implement the application(_:continueUserActivity:restorationHandler:) method in the AppDelegate class as shown below.

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, implement the restoreUserActivityState(_:) method in the MasterViewController class.

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)
    }
}

At the time of writing, the latest version of Xcode 7 (Beta 3) contains an issue where the userInfo property of a user activity being restored can be empty. That is why I handle any errors and display an alert with the userInfo that is returned by the operating system.

Build and run your app again, and search for a show. When you tap on a show in the search results, the app should take you straight to the detail view controller and display the current information for the show you tapped.

Application being restored correctly

Another set of APIs available in iOS 9 to make your content searchable for users is the Core Spotlight framework. This framework has a database-style design and lets you provide even more information about the content that you want to be searchable.

Before you can use the Core Spotlight framework, we need to link the project against the framework. In the Project Navigator, select the project and open theBuild Phases tab at the top. Next, expand the Link Binary With Libraries section and click the plus button. In the menu that appears, search for CoreSpotlight and link your project against the framework. Repeat these steps for the MobileCoreServices framework.

Adding the CoreSpotlight framework

Next, to ensure that the search results our app provides are from Core Spotlight, delete your app from your test device or the iOS Simulator and comment out the following line in the DetailViewController class:

activity.becomeCurrent()

Finally, open MasterViewController.swift and add the following lines before the Show structure definition:

import CoreSpotlight
import MobileCoreServices

Next, add the following code to the viewDidLoad method of theMasterViewController class:

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 in
    if error != nil {
        print(error?.localizedDescription)
    }
    else {
        // Items were indexed successfully
    }
}

Before we test this code, let's go through each step of the for loop.

  1. You create a CSSearchableItemAttributeSet object, passing in a content type for the item. If your search result links to a photo, for example, you would pass in the kUTTypeImage constant.
  2. You assign the show's name to the title property of the attribute set. Just like with NSUserActivity, this title is what will appear at the top of your search results.
  3. Next, you create a descriptive string and assign this to the contentDescription property of your searchable attribute set. This string will be displayed below the result's title in Spotlight.
  4. You create an array of keywords form the search result just as you did with NSUserActivity.
  5. Lastly, you create a CSSearchableItem with a unique item identifier, unique domain identifier to group items together, and an attribute set. Unlike with NSUserActivity, which returns the user activity from the search result, the unique identifiers you use for a CSSearchableItem are the only information that you receive from the operating system when your search result is selected by the user. You need to use these identifiers to restore your app back to the correct state.

Once you have created a CSSearchableItem for the TV shows, you index them using the indexSearchableItems(_:completionHandler:) method on the default CSSearchableIndex object.

Build and run your app, and all of your shows will be indexed by Spotlight. Navigate to the search view and search for one of the shows.

CoreSpotlight search result

Core Spotlight search results are handled by the same methods as those from NSUserActivity, but the process is slightly different. When a CSSearchableItem is selected from the search results, the system creates an NSUserActivity object for you that contains the unique identifier of the selected item.

In your app delegate's application(_:continueUserActivity:restorationHandler:)method, you can use the following implementation to retrieve the information you need from a Core Spotlight search result:

if userActivity.activityType == CSSearchableItemActionType {
    if let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
         
        //  Use identifier to display the correct content for this search result
         
        return true
    }
}

A good practice when indexing content from your app with the Core Spotlight framework is to also delete your items when they are no longer needed. The CSSearchableIndex class provides three methods to delete searchable items:

  • deleteAllSearchableItemsWithCompletionHandler(_:)
  • deleteSearchableItemsWithDomainIdentifiers(_:completionHandler:)
  • deleteSearchableItemsWithIdentifiers(_:completionHandler:)

As an example, add the following code to the end of the viewDidLoad method of the MasterViewController class:

CSSearchableIndex.defaultSearchableIndex().deleteSearchableItemsWithDomainIdentifiers(["tv-shows"]) { (error) -> Void in
    if error != nil {
        print(error?.localizedDescription)
    }
    else {
        // Items were deleted successfully
    }
}

Build and run your app one more time. When you try to search for any of your shows, no results are returned, because they have been deleted from the index.

Another new addition to the NSUserActivity class in iOS 9 is the contentAttributeSet property. This property allows you to assign a CSSearchableItemAttributeSet, just like the ones you created earlier. This attribute set allows your search results for NSUserActivity objects to show the same amount of detail as Core Spotlight search results.

Start by adding the following imports at the top of DetailViewController.swift:

import CoreSpotlight
import MobileCoreServices

Next, update the configureView method in the DetailViewController class with the following implementation:

func configureView() {
    // Update the 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 your app one final time, and open a few of your shows. When you now search for a show, you will see that your results, created with NSUserActivity, contain the same level of detail as the Core Spotlight search results.

NSUserActivity result with CoreSpotlight detail

Introducing Search APIs in iOS 9

Introducing Search APIs in iOS 9 Posted on 18-01-2016  This tutorial requires that you are running Xcode 7 on OS X 10.10 or later. To follow along with me, you also need to download the starter project from GitHub. 4.5/10 342

Comment:

To comment you must be logged in members.

Files with category

 
Newsletter Email

File suggestion for you

File top downloads

logo codetitle
Codetitle.com - library source code to share, download the file to the community
Copyright © 2015. All rights reserved. codetitle.com Develope by Vinagon .Ltd