Introduction

Modern mobile users expect instant access to information. Integrating system‑level search into your iOS app dramatically reduces friction, allowing users to find content without opening your app first. The Core Spotlight API is Apple’s built‑in framework for indexing app content into the device’s global Spotlight search. When a user searches from the Home screen or within the Search view, your app’s indexed items appear alongside results from Mail, Messages, and other system apps. This article provides a comprehensive, production‑ready guide to implementing Core Spotlight, covering everything from basic indexing to deep linking and performance tuning.

Understanding Core Spotlight and Its Benefits

Core Spotlight is a lightweight, privacy‑friendly indexing system. It lives entirely on‑device – no data leaves the user’s phone unless you explicitly use CloudKit. This makes it ideal for personal content such as notes, contacts, documents, or any user‑generated data. The primary benefits include:

  • Increased engagement – Users discover content they might have forgotten, boosting repeat usage.
  • Seamless navigation – Tapping a search result can open your app directly to the relevant screen.
  • Offline capability – Indexed items remain searchable even without internet connectivity.
  • Low implementation cost – The API is small, well‑documented, and integrates easily with existing data models.

Core Spotlight works alongside NSUserActivity indexing, but for static or frequently updated content, direct CSSearchableItem indexing gives you finer control.

Prerequisites and Setup

Before writing code, ensure your project is configured correctly:

  1. Your app must target iOS 9 or later (the API debuted with iOS 9).
  2. Enable Core Spotlight capability in Xcode – under Signing & Capabilities add the “Core Spotlight” entitlement. This is often automatically included when importing the framework.
  3. Import the Core Spotlight framework and MobileCoreServices (for type identifiers): import CoreSpotlight and import MobileCoreServices.

No additional server configuration or user permission is needed. Indexing happens asynchronously in the background.

Creating and Indexing Searchable Items

The core of Core Spotlight is the CSSearchableItem object. Each item consists of:

  • A unique identifier (e.g., com.yourapp.content.123) – used later to handle selection or updates.
  • An optional domain identifier – groups related items (e.g., com.yourapp.notes).
  • An attribute set (CSSearchableItemAttributeSet) – describes the content with properties like title, contentDescription, keywords, thumbnailData, and more.

Here’s a complete Swift example that indexes a single note:

import CoreSpotlight
import MobileCoreServices

func indexNote(note: Note) {
    let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String)
    attributeSet.title = note.title
    attributeSet.contentDescription = note.body
    attributeSet.keywords = [note.title] + note.tags
    if let imageData = note.thumbnail?.pngData() {
        attributeSet.thumbnailData = imageData
    }
    
    let item = CSSearchableItem(
        uniqueIdentifier: "com.yourapp.note.\(note.id)",
        domainIdentifier: "com.yourapp.notes",
        attributeSet: attributeSet
    )
    // Optionally set expiration date (default is one month)
    // item.expirationDate = Date.distantFuture
    
    CSSearchableIndex.default().indexSearchableItems([item]) { error in
        if let error = error {
            print("Indexing error: \(error.localizedDescription)")
        } else {
            print("Note indexed successfully.")
        }
    }
}

Attribute Set Properties

The CSSearchableItemAttributeSet inherits from NSObject and offers dozens of properties. For most apps the following are essential:

  • title – Short, descriptive text that appears prominently.
  • contentDescription – A longer snippet shown below the title.
  • keywords – Array of String for alternate search terms.
  • thumbnailData or thumbnailURL – Visual preview.
  • relatedUniqueIdentifier – Links items together (e.g., parent‑child notes).

For media content, use type‑specific properties like artist, album, or duration.

Updating and Deleting Indexed Content

When your app’s content changes, you must keep the index in sync. Use the same CSSearchableIndex methods:

Updating an Item

Call indexSearchableItems with the same unique identifier – the API automatically replaces the old entry.

Deleting Items

Use deleteSearchableItems(withIdentifiers:) or deleteSearchableItems(withDomainIdentifiers:) to remove stale content:

CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ["com.yourapp.note.123"]) { error in
    // handle
}

Batch operations are efficient: pass arrays to indexSearchableItems or deleteSearchableItems. For a full reset, use deleteAllSearchableItems (rarely needed).

Handling User Activity and Deep Linking

When a user taps a Spotlight result, your app receives a call through the NSUserActivity continuation. Implement the following delegate method in your AppDelegate or SceneDelegate:

func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    
    if userActivity.activityType == CSSearchableItemActionType {
        if let userInfo = userActivity.userInfo,
           let identifier = userInfo[CSSearchableItemActivityIdentifier] as? String {
            // Navigate to the item with this identifier
            navigateToContent(identifier: identifier)
            return true
        }
    }
    return false
}

Restoring State

For a smoother experience, also implement application(_:willContinueUserActivityWithType:) to show a loading state. In scene‑based apps (iOS 13+), use the UISceneDelegate method scene(_:continue:).

Deep Linking with NSUserActivity

Core Spotlight also works with the NSUserActivity mechanism. If you already index activities via userActivity, you can add the isEligibleForSearch flag. However, for fine‑grained control over titles and descriptions, use CSSearchableItem directly.

Best Practices for Effective Indexing

Following these guidelines ensures your indexed content remains relevant and performant:

1. Index Only Meaningful Content

Don’t index every tiny piece of data. Focus on items users are likely to search for: recent documents, high‑value pages, or bookmarked content.

2. Set Proper Expiration Dates

By default, items expire after one month. For static content, set expirationDate = Date.distantFuture. For time‑sensitive data (e.g., event reminders), use a realistic date.

3. Use Domain Identifiers Wisely

Domain identifiers allow you to delete all items from a group at once. For example, if a user deletes a folder, call deleteSearchableItems(withDomainIdentifiers: ["com.yourapp.folder.456"]).

4. Provide Meaningful Keywords

Include synonyms, common misspellings, and alternative names. Avoid overstuffing; 5–10 well‑chosen keywords are enough.

5. Avoid Indexing Duplicate Content

If you duplicate items (e.g., the same title in different domains), users see confusing results. Deduplicate at the app level.

6. Batch Operations for Bulk Updates

When syncing many items (e.g., after a cloud restore), batch them into arrays of 50–100 to avoid blocking the main thread. Use indexSearchableItems with an array, not multiple single calls.

Performance and Battery Considerations

Core Spotlight runs a low‑priority background process. But poor implementation can drain battery or degrade system performance:

  • Limit reindexing frequency – Only reindex when data actually changes, not on every app launch.
  • Throttle large operations – Use beginIndexBatch and endIndexBatch (available on CSSearchableIndex) for atomic updates.
  • Avoid heavy imagesthumbnailData should be small (e.g., 64×64 points, JPEG compressed). Large images increase memory pressure.
  • Use the default indexCSSearchableIndex.default() is sufficient for almost all apps. Creating custom indexes is rarely needed.

Apple’s Core Spotlight documentation emphasises that indexing should be treated as “fire and forget” – do not synchronously wait for completion callbacks on the main thread.

Testing Core Spotlight Integration

Debugging Spotlight results can be tricky because they don’t always appear immediately. Follow this testing strategy:

  1. Simulator testing – Use iOS Simulator (iOS 15 or later). After indexing, wait a few seconds, then pull down the Home screen to search. Be aware that results may take longer in the Simulator.
  2. Device testing – Always test on a physical device. Spotlight indexing behaves more aggressively on real hardware.
  3. Reset the index – Use the Settings app: General → Spotlight Search → toggle your app off and on. This clears the index and forces a fresh start.
  4. Verify deletion – After deleting an item, ensure the old result no longer appears. Sometimes the system caches results; give it up to 30 seconds.
  5. Check the debug console – Enable com.apple.CoreSpotlight logging by passing -com.apple.CoreSpotlight LogLevel 7 in Xcode’s scheme arguments.

A useful resource is WWDC 2015 “Introducing Core Spotlight”, which covers foundational concepts still applicable today.

Conclusion

The Core Spotlight API is a robust, underutilised tool that can transform how users interact with your app. By adding relevant, up‑to‑date content to the system’s search index, you provide a friction‑less path from search to content – boosting engagement and retention. Start small: index your most important content first, test thoroughly, and then expand to cover secondary items. Remember to keep your index clean, use unique identifiers consistently, and handle user activity callbacks gracefully. With the steps outlined in this article, you’re ready to deliver a first‑class search experience that feels native to iOS.

For further reading, consult the official Core Spotlight Framework Reference and the App Search Programming Guide.