Search Content Of Your Application With SpotLight

SpotLight

Ever wondered about why there is only search for app names and not the contents within it?
iOS 9.X allows you to search for the app contents without even opening the app.
Here, we are going to create an awesome application which allows you to search contents of your application through this spotlight feature.
Why we are introducing you to spotlight ahead of the iOS 10 launch?
There are several changes in syntax of swift 2.0. So If there is problem in automatic code conversion, you can go ahead with our blog.
Before we go further, I would like to say this demo project should contain some data which can be searched with the spotlight feature. If you have your own data then its good, But If you are working on a demo and finding some dummy data the you can download the starter project from here.
This starter project contains information which we are going to search in the spotlight feature. It has a MoviesData.plist file. This file contains the movie names which we are going to search by their category, name etc.
There are some movie posters for knowing what we are searching and to know what it looks like when we get some images to display.
Other configuration includes storyboard file with viewControllers and one tableView’s XIB  for custom cell. We are not going into the detail of how to give constraints and assign the view controllers to the storyboards.
Basic frameworks need to complete this demo project are coreSpotlight.framework and MobileCoreServices.framework. We have also added these two for you.

Code:

Data that we included in the starter project, need to be initialized in the application. Unless we create an object of that data, it is considered as dead data. Adding the following lines will create an array of those movie.

func loadMoviesInfo() {

       if let path = Bundle.main.path(forResource: “MoviesData”, ofType: “plist”) {

           moviesInfo = NSMutableArray(contentsOfFile: path)

       }

   }

If you tried running the starter project, you will get to see nothing on the view. There is the need of showing the data which we are storing in the MoviesData.plist.
call the above function in viewDidLoad method as the whole method will look like:

override func viewDidLoad() {

       super.viewDidLoad()

       // Do any additional setup after loading the view, typically from a nib.

       

       configureTableView()

       navigationItem.title = “Movies”

       

       loadMoviesInfo ()

   }

We need to add some code in tableView datasource to populate the data. Firstly, we need to decide numberOfRowsInSection by providing the array’s count.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

       if moviesInfo != nil {

           return moviesInfo.count

       }

       return 0

   }

In cellForRowAt indexPath method, we have to initialize the custom cell by providing its cellIdentifier.

let cell = tableView.dequeueReusableCell(withIdentifier: “idCellMovieSummary”, for: indexPath) as! MovieSummaryCell

again, for each index of the cell, we have to set the objects according to their index number.

let currentMovieInfo = moviesInfo[indexPath.row] as! [String: String]

       

       cell.lblTitle.text = currentMovieInfo[“Title”]!

       cell.lblDescription.text = currentMovieInfo[“Description”]!

       cell.lblRating.text = currentMovieInfo[“Rating”]!

       cell.imgMovieImage.image = UIImage(named: currentMovieInfo[“Image”]!)

Now Compile and run the project. You will be able to see the movies listed in the MoviesData.plist
Clicking on any of the cell should show the details of the movie in the next view that we designed. For this, we need to get the selected index of the cell, and the data in that index of  moviesInfo array should be passed to next. Declaring a selectedIndex variable as below in the project will help us in this.
var selectedMovieIndex: Int!
But from where should we get the selected Index value? Here it is:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

       

       selectedMovieIndex = (indexPath as NSIndexPath).row

       performSegue(withIdentifier: “idSegueShowMovieDetails”, sender: self)

   }

Finally, overriding the prepareForSegue method to perform the said operation iscreated.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

       if let identifier = segue.identifier {

           if identifier == “idSegueShowMovieDetails” {

               let movieDetailsViewController = segue.destination as! MovieDetailsViewController

               movieDetailsViewController.movieInfo = moviesInfo[selectedMovieIndex] as! [String : String]

           }

       }

   }

Run your project to see if it works.
Now create a function for actual code for opting in into the SpotLight feature.
Starting with importing the frameworks, We have to create a function to  set the searchable spotlight data.

import CoreSpotlight

import MobileCoreServices

Create a function named setupSearchableContent.

func setupSearchableContent()

   {

   }

In the above function, we need to set the plist contents for making the items available to the spotlight search.
As the Apple’s official documentation says, “You can use the properties that Core Spotlight provides in categories defined on CSSearchableItemAttributeSet (such as Media and Documents), or you can define your own.”
We can set the media(images here) and the movie categories as spotlight metadata.
Lets learn the code step by step now.

var searchableItems = [CSSearchableItem]() // 1

        for i in 0…(moviesInfo.count – 1) {

           let movie = moviesInfo[i] as! [String: String] //2

           let searchableItemAttributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String) //3

           searchableItemAttributeSet.title = movie[“Title”]! //4

           let imagePathParts = movie[“Image”]!.components(separatedBy: “.”) // 5

           searchableItemAttributeSet.thumbnailURL = Bundle.main.url(forResource: imagePathParts[0], withExtension: imagePathParts[1])

           // Set the description.

           searchableItemAttributeSet.contentDescription = movie[“Description”]! //6

           var keywords = [String]()

           let movieCategories = movie[“Category”]!.components(separatedBy: “, “) //7

           for movieCategory in movieCategories {

               keywords.append(movieCategory)

           }          

           let stars = movie[“Stars”]!.components(separatedBy: “, “)

           for star in stars {

               keywords.append(star) //8

           }          

           searchableItemAttributeSet.keywords = keywords

           let searchableItem = CSSearchableItem(uniqueIdentifier: “com.mindbowser.SpotIt.\(i)”, domainIdentifier: “moviesSpotIt”, attributeSet: searchableItemAttributeSet) //9         

           searchableItems.append(searchableItem)

1. We created an CSSearchableItem’s object. We are going to push searchable objects one by one.
2. For traversing an array, we have to use a loop. 3-8.  In loop, we are-providing the searchable data as an attribute set. In that movie title, movie image, description, category and its ratings are provided.
3. Now we initialize the searchable items with passing the three attributes:
uniqueIdentifier : Unique identifier is unique for each app. We are passing index of the listing to the this parameter. Because we need it later.
domainIdentifier : Domain Identifier is more generalized than the uniqueIdentifier. it makes your apps searchable while searching for the similar apps in device.
attributeSet : We pass the composed attribute set with the keywords.

CSSearchableIndex.default().indexSearchableItems(searchableItems) { (error) -> Void in

           if error != nil {

               print(error?.localizedDescription)

           }

       }

   }

Adding the above lines of code in the same function will return you the exact error for the application when its not available for the spotlight.
Call this function from viewDidload.
Now compile and run the app. Minimize it by pressing home button of the device. Try searching the app content using spotlight. You see the content. but after taping the search result, we are not able to open the desired view.
Here are the steps to correct it. Add the following line of code in the viewController. If you are handling multiple types for the spotlight feature, then it is required to check the CSSearchableItemActionType.

override func restoreUserActivityState(_ activity: NSUserActivity) {

       if activity.activityType == CSSearchableItemActionType {

           if let userInfo = activity.userInfo {

               let selectedMovie = userInfo[CSSearchableItemActivityIdentifier] as! String

               selectedMovieIndex = Int(selectedMovie.components(separatedBy: “.”).last!)

               performSegue(withIdentifier: “idSegueShowMovieDetails”, sender: self)

           }

       }

   }

Open your AppDelegate.swift And perform the following changes in order to send pass the desired userActivity.

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {

      

       let viewController = (window?.rootViewController as! UINavigationController).viewControllers[0] as! ViewController

       viewController.restoreUserActivityState(userActivity)

       

       return true

   }

Compile and Run your app and your demo app is ready.
Happy coding..!!

Leave a Comment