Реалізація інтеграції Spotlight Search для iOS-додатку
Spotlight — системний поиск iOS, доступний свайпом вниз з домашньої сторінки. Якщо проіндексувати контент додатку, користувач знайде його прямо в Spotlight без запуску додатку. Натиск на результат — deep link в потрібний екран.
Три API для індексації: CSSearchableIndex для користувацького контенту, NSUserActivity для поточних активностей, CoreSpotlight + AppIntents для інтеграції зі Shortcuts (iOS 16+).
CSSearchableIndex: основний API
Кожен елемент — CSSearchableItem з унікальним uniqueIdentifier, domainIdentifier (групування за типом) і CSSearchableItemAttributeSet (метаданні).
let attributeSet = CSSearchableItemAttributeSet(contentType: .text)
attributeSet.title = article.title
attributeSet.contentDescription = article.summary
attributeSet.keywords = article.tags
attributeSet.thumbnailData = await fetchThumbnail(article.imageURL)
let item = CSSearchableItem(
uniqueIdentifier: "article-\(article.id)",
domainIdentifier: "articles",
attributeSet: attributeSet
)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error { print("Index error:", error) }
}
uniqueIdentifier — це той же рядок, який ви отримаєте в application(_:continue:restorationHandler:) коли користувач тапне результат. По ньому визначаємо, який екран відкрити.
Пакетна індексація. Індексувати по одному елементу при кожному завантаженні — погана ідея. При великому каталозі (тисячи елементів) використовуємо CSSearchableIndex.fetchLastClientState() / beginBatch() / endBatch() — це трансакційна індексація, яка швидша і не викликає лишніх re-index.
NSUserActivity для недавніх дій
NSUserActivity автоматично індексує те, що користувач робив у додатку — «недавно переглянуті» в Spotlight. Плюс це основа для Handoff між пристроями.
let activity = NSUserActivity(activityType: "com.yourapp.viewArticle")
activity.title = article.title
activity.userInfo = ["articleId": article.id]
activity.isEligibleForSearch = true
activity.isEligibleForHandoff = true
activity.isEligibleForPrediction = true // для Siri Suggestions
activity.becomeCurrent()
Викликаємо becomeCurrent() у viewDidAppear, resignCurrent() у viewDidDisappear. Часта помилка: викликати becomeCurrent() у viewDidLoad — активність реєструється до того, як користувач реально побачив контент.
Обробка deep link з Spotlight
У AppDelegate:
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == CSSearchableItemActionType {
let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String
// Navigate to item with identifier
}
return true
}
У SwiftUI/SceneDelegate — через модифікатор .onContinueUserActivity(CSSearchableItemActionType).
Міграція зі SceneDelegate. На iOS 13+ додаток може мати кілька scene. Якщо Spotlight відкриває додаток з нуля — scene(_:willConnectTo:options:) отримує userActivity у connectionOptions.userActivities. Якщо додаток уже запущений — scene(_:continue:). Потрібно обробляти обидва випадки.
Видалення та оновлення індексу
Застарілий контент у Spotlight дратує користувачів: вони тапають — додаток відкривається на 404. Видаляємо через CSSearchableIndex.default().deleteSearchableItems(withIdentifiers:) або deleteSearchableItems(withDomainIdentifiers:) для цілого типу. При видаленні користувацьких даних — видаляємо з індексу в тому ж потоці.
Не індексуємо приватний контент без явного згоди користувача — Apple перевіряє це на review через HIG.
Розклад
Базова індексація основного контенту: 1–2 тижні. Повна інтеграція з NSUserActivity, пакетною індексацією, обробкою deep link та актуалізацією індексу: 3–5 тижнів. Вартість залежить від обсягу каталогу та складності deep link схеми.







