Making React Native more native: bringing iOS Spotlight to Headout

Ever wished you could find your Headout tickets without even opening the app? That’s exactly what we set out to solve.

At Headout, we’re always looking for ways to make things faster, simpler, and more intuitive for our users. Two years ago, we switched to React Native, and it has been an absolute game-changer—helping us ship features faster, improve developer productivity, and maintain a single codebase across iOS and Android.

But while React Native is fantastic for cross-platform development, it does have its limitations—especially when dealing with platform-specific features like iOS’s Spotlight Search. Since React Native doesn’t natively support Spotlight, we had to get creative. Enter Turbo Modules—a powerful way to extend React Native’s capabilities and integrate deep system-level features.


The challenge: Making bookings instantly searchable

Spotlight Search is one of iOS’s most underrated yet powerful features. It lets users quickly search for apps, contacts, messages, and even deeply indexed content within apps. We wanted to leverage this to:

  • Allow users to search for their bookings directly from Spotlight
  • Show relevant ticket details like title, description, and thumbnail
  • Make the experience instantaneous and seamless

The problem? React Native doesn’t support Spotlight indexing out of the box. We needed a solution that would allow us to bridge the gap between our React Native app and iOS’s native Spotlight APIs.


Why Turbo Modules? 🤔

Historically, when React Native developers needed to access platform-specific features, we built Native Modules—bridges between JavaScript and native platform code. These worked well, but they had drawbacks:

  • Native Modules ran on the main thread, potentially blocking UI updates.
  • They required synchronous communication, making them slower and prone to performance bottlenecks.
  • Each module had to be manually linked, adding complexity to the build process.

With the introduction of React Native’s new architecture, Turbo Modules solve these issues by:

  • Running on a separate thread, improving app performance
  • Using JSI (JavaScript Interface) for direct, synchronous access to native code
  • Enabling lazy loading, so modules are initialised only when needed

Turbo Modules gave us a modern, optimised way to implement Spotlight search without compromising React Native’s performance.


Building the Turbo Module for spotlight

To bring Spotlight indexing into our React Native app, we built a Swift-based Turbo Module that acts as a bridge. This module allows us to send booking data from React Native to iOS, which then indexes the data into Spotlight.

Core function: indexing tickets in spotlight

At the heart of this module is the indexTicket function, which takes the following parameters:

  • id – A unique identifier for the search result
  • title – The main title displayed in Spotlight
  • description – Additional details shown under the title
  • thumbnailUrl – The image shown in the search result

Here’s how we implemented it in Swift:

@objc
func indexTicket(
        _ id: String, 
        title: String, 
        description: String, 
        thumbnailUrl: String, 
        resolver: @escaping RCTPromiseResolveBlock, 
        rejecter: @escaping RCTPromiseRejectBlock
) {
        if #available(iOS 14, *) {
            let attributeSet = CSSearchableItemAttributeSet(contentType: .item)
            let queries = ["Ticket for ", "Booking for ", "Experience ", "Headout "]
            attributeSet.keywords = queries.map { "\($0) \(title)" }
            attributeSet.title = title
            attributeSet.contentDescription = description
            attributeSet.creator = "Headout Inc"
            attributeSet.subject = "Ticket"
            attributeSet.displayName = title
            
            guard let url = URL(string: thumbnailUrl) else { resolver(false); return }
            do {
                let data = try Data(contentsOf: url)
                let thumbnail = UIImage(data: data)
                attributeSet.thumbnailData = thumbnail?.pngData()
            } catch {
                resolver(false)
                return
            }

            let item = CSSearchableItem(uniqueIdentifier: id, domainIdentifier: "headout_ticket", attributeSet: attributeSet)
            CSSearchableIndex.default().indexSearchableItems([item]) { (error) in
                resolver(error == nil)
            }
        } else {
            resolver(false)
        }
    }
Diagram: Booking to Spotlight Data Flow

With this function, our React Native app can send booking data to iOS, which then indexes it into Spotlight, making tickets instantly searchable!


Enhancing the Spotlight preview

While Spotlight indexing worked beautifully, we ran into an issue:

💡 Long-pressing a search result in Spotlight opened a generic preview box with just the title and subtitle.

We wanted to display full booking details in the preview, but fetching data dynamically would mean extra API calls—not ideal for speed.

Our hack: encoding booking data in the ID

1️⃣ We created a map containing all the essential booking details:

const ticketPreviewInfo = {
    bookingId: bookingVoucher.bookingID,
    experienceName: bookingVoucher.tour.tourGroupName,
    experienceDate: date.format('DD MMM, YYYY'),
};

2️⃣ We encoded this map as a Base64 string and used it as the id:

const encodedId = Buffer.from(JSON.stringify(ticketPreviewInfo)).toString('base64');

3️⃣ On the Swift side, we added the QuickLook framework in our app and linked it with CoreSpotlight. Inside the PreviewViewController we decoded this Base64 id string and extracted the booking details when rendering the preview!

This turned each indexed Spotlight item into a self-contained data container, making previews instantaneous and eliminating unnecessary API calls.

The result 🤩

0:00
/0:21

Spotlight Demo in Headout iOS App

Final thoughts: why this matters

By leveraging Turbo Modules, we were able to bridge React Native with iOS spotlight search, unlocking a powerful, native-like search experience for our users.

This project was a perfect example of how React Native’s limitations don’t have to be blockers—with the right tools, we can extend its capabilities and build features that feel completely native.

So if you’re working on a React Native app and feel constrained by missing platform features, Turbo Modules might just be the key to unlocking your next big feature! 🚀

Hardik Srivastava’s Profile Image

written by Hardik Srivastava

React Native and Android enthusiast. Everything Apps @Headout. 🚀

Dive into more stories