Статьи

iOS 9: внедрение API поиска

На WWDC 2015 Apple официально анонсировала iOS 9 . В дополнение ко многим новым функциям и улучшениям, это обновление также дает разработчикам возможность сделать контент своих приложений более доступным и доступным с помощью поиска Spotlight. Новые API, доступные в iOS 9, позволяют индексировать любой контент или состояние интерфейса в вашем приложении, делая его доступным для ваших пользователей через Spotlight. Три компонента этих новых API поиска:

  • класс NSUserActivity , предназначенный для просмотра содержимого приложения
  • платформа Core Spotlight , предназначенная для любого содержимого приложения
  • веб-разметка, предназначенная для приложений, содержимое которых отображается на веб-сайте

В этом руководстве я собираюсь показать вам, как вы можете использовать класс NSUserActivity и инфраструктуру Core Spotlight в своих собственных приложениях.

Это руководство требует, чтобы вы работали с Xcode 7 в OS X 10.10 или более поздней версии. Чтобы следовать за мной, вам также нужно скачать стартовый проект с GitHub .

В первой части этого руководства я покажу вам, как можно индексировать содержимое приложения с помощью класса NSUserActivity . Этот API является тем же, который используется для Handoff, функции, представленной в iOS 8 в прошлом году, и обрабатывает как сохранение, так и восстановление текущего состояния приложения.

Если вы раньше не работали с NSUserActivity , то я предлагаю вам сначала прочитать мой учебник, посвященный основам NSUserActivity и NSUserActivity затем продолжить.

Прежде чем писать какой-либо код, откройте стартовый проект и запустите приложение в iOS Simulator или на тестовом устройстве. На этом этапе вы увидите, что приложение просто отображает список из четырех телешоу и страницу с подробностями для каждого из них.

Показать список
Показать детали

Для начала откройте стартовый проект и перейдите к DetailViewController.swift . Замените метод DetailViewController класса DetailViewController следующей реализацией:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()
    }
}

Код, который настраивает метки в контроллере представления, не изменяется, но давайте пройдемся по коду активности пользователя шаг за шагом:

  1. Вы создаете новый объект NSUserActivity с уникальным идентификатором com.tutsplus.iOS-9-Search.displayShow . Стартовый проект уже настроен на использование этого идентификатора, поэтому обязательно оставьте этот идентификатор без изменений.
  2. Затем вы назначаете словарь userInfo пользовательской активности. Это будет использовано позже для восстановления состояния приложения.
  3. Вы присваиваете свойству title активности строковое значение. Это то, что будет отображаться в результатах поиска Spotlight.
  4. Чтобы обеспечить возможность поиска по контенту не только по его названию, вы также предоставляете набор ключевых слов. В приведенном выше фрагменте кода набор ключевых слов включает в себя каждое слово из названия шоу, а также его жанр.
  5. Затем вы устанавливаете ряд свойств объекта NSUserActivity чтобы сообщить операционной системе, для чего вы хотите использовать это действие пользователя. В этом уроке мы рассматриваем только компонент поиска API, поэтому мы отключаем Handoff и включаем поиск .
  6. Наконец, вы вызываете метод becomeCurrent для активности пользователя, после чего она автоматически добавляется в индекс результатов поиска устройства.

В приведенной выше реализации вы, вероятно, заметили две строки в комментариях. Хотя мы не будем использовать эти свойства в этом руководстве, важно знать, для чего используется каждое свойство.

  • В вышеописанной реализации активность пользователя и результат поиска создаются для каждого отдельного шоу только после открытия приложения. Когда вы делаете свою пользовательскую активность eligibleForPublicIndexing , Apple начинает отслеживать использование и взаимодействие этой конкретной деятельности из результатов поиска пользователя. Если результатом поиска пользуются многие пользователи, Apple продвигает активность пользователей в свой собственный облачный индекс . Как только пользовательский актив попадает в этот облачный индекс, он доступен для поиска любому, кто установил ваше приложение, независимо от того, открыли ли они конкретный контент или нет. Это свойство должно иметь значение true для действий, доступных всем пользователям вашего приложения.
  • Пользовательское действие также может иметь необязательную expirationDate . Когда это свойство установлено, ваша пользовательская активность будет отображаться в результатах поиска только до указанной даты.

Теперь, когда вы знаете, как создать NSUserActivity способное отображать результаты поиска в Spotlight, вы готовы его протестировать. Создайте и запустите свое приложение, и откройте несколько шоу в своем приложении. Сделав это, вернитесь на начальный экран (нажмите Command-Shift-H в симуляторе iOS) и проведите пальцем вниз или прокрутите до крайнего левого экрана, чтобы открыть вид поиска.

Начните вводить название одной из открытых вами выставок, и вы увидите, что она появляется в результатах поиска, как показано ниже.

Начните вводить заголовок одного из шоу

Или введите жанр одного из шоу, которое вы открыли. Из-за ключевых слов, которые вы присвоили активности пользователя, это также приведет к тому, что шоу будет отображаться в результатах поиска.

Введите жанр одного из шоу

Содержимое вашего приложения правильно проиндексировано операционной системой, и результаты отображаются в Spotlight. Однако при нажатии на результат поиска ваше приложение не приводит пользователя к соответствующему результату поиска. Он просто запускает приложение.

К счастью, как и в случае с Handoff , вы можете использовать класс NSUserActivity для восстановления правильного состояния в вашем приложении. Чтобы сделать эту работу, нам нужно реализовать два метода.

AppDelegate application(_:continueUserActivity:restorationHandler:) в классе AppDelegate как показано ниже.

1
2
3
4
5
6
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
    let splitController = self.window?.rootViewController as!
    let navigationController = splitController.viewControllers.first as!
    navigationController.topViewController?.restoreUserActivityState(userActivity)
    return true
}

Затем реализуйте метод restoreUserActivityState(_:) в классе MasterViewController .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
override func restoreUserActivityState(activity: NSUserActivity) {
    if let name = activity.userInfo?[«name»] as?
        let genre = activity.userInfo?[«genre»] as?
        let time = activity.userInfo?[«time»] as?
        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)
    }
}

На момент написания этой статьи последняя версия Xcode 7 (бета-версия 3) содержала проблему, из-за userInfo свойство userInfo действия пользователя может быть пустым. Вот почему я обрабатываю любые ошибки и отображаю предупреждение с userInfo , которое возвращается операционной системой.

Снова создайте и запустите приложение и выполните поиск шоу. Когда вы нажимаете на шоу в результатах поиска, приложение должно сразу перейти к контроллеру подробного представления и отобразить текущую информацию для шоу, которое вы нажали.

Приложение восстанавливается правильно

Еще один набор API-интерфейсов, доступных в iOS 9, чтобы сделать ваш контент доступным для поиска для пользователей, — это платформа Core Spotlight . Эта структура имеет дизайн в стиле базы данных и позволяет вам предоставлять еще больше информации о контенте, который вы хотите использовать для поиска.

Прежде чем вы сможете использовать платформу Core Spotlight, нам нужно связать проект со структурой. В Навигаторе проектов выберите проект и откройте вкладку « Фазы сборки » вверху. Затем разверните раздел « Связать двоичные файлы с библиотеками » и нажмите кнопку «плюс». В появившемся меню найдите CoreSpotlight и свяжите ваш проект со структурой. Повторите эти шаги для платформы MobileCoreServices .

Добавление платформы CoreSpotlight

Затем, чтобы убедиться, что результаты поиска, которые предоставляет наше приложение, получены из Core Spotlight, удалите свое приложение с тестового устройства или симулятора iOS и закомментируйте следующую строку в классе DetailViewController :

1
activity.becomeCurrent()

Наконец, откройте MasterViewController.swift и добавьте следующие строки перед определением структуры Show :

1
2
import CoreSpotlight
import MobileCoreServices

Затем добавьте следующий код в метод MasterViewController класса MasterViewController :

01
02
03
04
05
06
07
08
09
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 in
    if error != nil {
        print(error?.localizedDescription)
    }
    else {
        // Items were indexed successfully
    }
}

Прежде чем мы протестируем этот код, давайте пройдемся по каждому шагу цикла for .

  1. Вы создаете объект CSSearchableItemAttributeSet , передавая тип содержимого для элемента. Например, если ваш результат поиска связан с фотографией, вы должны передать константу kUTTypeImage .
  2. Вы назначаете имя шоу свойству title набора атрибутов. Как и в случае NSUserActivity , этот заголовок будет отображаться в верхней части результатов поиска.
  3. Затем вы создаете описательную строку и назначаете contentDescription свойству contentDescription вашего набора атрибутов с возможностью поиска. Эта строка будет отображаться под заголовком результата в центре внимания.
  4. Вы создаете массив ключевых слов из результата поиска так же, как вы делали это с NSUserActivity .
  5. Наконец, вы создаете CSSearchableItem с уникальным идентификатором элемента, уникальным идентификатором домена для группировки элементов и набором атрибутов. В отличие от NSUserActivity , который возвращает активность пользователя из результата поиска, уникальные идентификаторы, которые вы используете для CSSearchableItem являются единственной информацией, которую вы получаете из операционной системы, когда ваш результат поиска выбирается пользователем. Вам нужно использовать эти идентификаторы, чтобы вернуть ваше приложение в правильное состояние.

CSSearchableItem для телешоу, вы индексируете их с помощью indexSearchableItems(_:completionHandler:) CSSearchableIndex объекта CSSearchableIndex по умолчанию.

Создайте и запустите свое приложение, и все ваши шоу будут проиндексированы Spotlight. Перейдите к представлению поиска и найдите одно из шоу.

Результат поиска CoreSpotlight

Результаты поиска Core Spotlight обрабатываются теми же методами, что и NSUserActivity , но процесс немного отличается. Когда CSSearchableItem выбран из результатов поиска, система создает для NSUserActivity объект NSUserActivity , который содержит уникальный идентификатор выбранного элемента.

В приложении делегата вашего приложения application(_:continueUserActivity:restorationHandler:) метод application(_:continueUserActivity:restorationHandler:) вы можете использовать следующую реализацию для получения необходимой информации из результатов поиска Core Spotlight:

1
2
3
4
5
6
7
8
if userActivity.activityType == CSSearchableItemActionType {
    if let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as?
         
        // Use identifier to display the correct content for this search result
         
        return true
    }
}

При индексировании контента из приложения с помощью платформы Core Spotlight рекомендуется также удалять элементы, когда они больше не нужны. Класс CSSearchableIndex предоставляет три метода для удаления элементов поиска:

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

Например, добавьте следующий код в конец метода MasterViewController класса MasterViewController :

1
2
3
4
5
6
7
8
CSSearchableIndex.defaultSearchableIndex().deleteSearchableItemsWithDomainIdentifiers([«tv-shows»]) { (error) -> Void in
    if error != nil {
        print(error?.localizedDescription)
    }
    else {
        // Items were deleted successfully
    }
}

Создайте и запустите приложение еще раз. Когда вы пытаетесь найти любое из ваших шоу, результаты не возвращаются, потому что они были удалены из индекса.

Еще одно новое дополнение к классу NSUserActivity в iOS 9 — это свойство contentAttributeSet . Это свойство позволяет вам назначать CSSearchableItemAttributeSet , как и те, что вы создали ранее. Этот набор атрибутов позволяет вашим результатам поиска для объектов NSUserActivity отображать то же количество деталей, что и результаты поиска Core Spotlight.

Начните с добавления следующего импорта в начало файла DetailViewController.swift :

1
2
import CoreSpotlight
import MobileCoreServices

Затем обновите метод configureView в классе DetailViewController с помощью следующей реализации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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()
    }
}

Создайте и запустите приложение в последний раз и откройте несколько своих шоу. Теперь, когда вы будете искать шоу, вы увидите, что ваши результаты, созданные с помощью NSUserActivity , содержат тот же уровень детализации, что и результаты поиска Core Spotlight.

Результат NSUserActivity с деталями CoreSpotlight

Из этого руководства вы узнали, как сделать контент вашего приложения доступным через iOS Spotlight с помощью класса NSUserActivity и платформы Core Spotlight . Я также показал вам, как индексировать контент из вашего приложения с помощью обоих API и как восстанавливать состояние вашего приложения, когда пользователь выбирает результат поиска.

Новые API поиска, представленные в iOS 9, очень просты в использовании и делают контент вашего приложения более простым для поиска и более доступным для пользователей вашего приложения. Как всегда, если у вас есть какие-либо комментарии или вопросы, оставьте их в комментариях ниже.