All skills

Code generator skills that produce production-ready Swift code for common app components. Use when user wants to add logging, analytics, onboarding, review prompts, networking, authentication, paywalls, settings, persistence, error monitoring, CI/CD pipelines, localization, push notifications, deep linking, testing, accessibility, widgets, feature flags, app icons, image caching, pagination, HTTP caching, share cards, social export, subscription lifecycle, referral systems, watermarks, streak tracking, milestone celebrations, what's new screens, lapsed user re-engagement, usage insights, variable rewards, consent flows, account deletion, permission priming, force updates, state restoration, debug menus, offline queues, feedback forms, announcement banners, quick win sessions, Spotlight indexing, App Clips, screenshot automation, background processing, app extensions, data export, or SwiftUI preview sample data and variant matrices.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/generators

This session only. Nothing lands on disk.

spotlight-indexingtemplates.md

≈5.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Spotlight Indexing Code Templates

Production-ready Swift templates for Core Spotlight indexing infrastructure. All code targets iOS 16+ / macOS 13+ (iOS 17+ / macOS 14+ for @Observable) and uses modern Swift concurrency.

SpotlightIndexable.swift

import Foundation
import CoreSpotlight

/// Protocol that any model can conform to for Spotlight indexing.
///
/// Provides all the data needed to create a `CSSearchableItem`.
/// Conform your models to this protocol and pass them to `SpotlightIndexManager`.
///
/// Usage:
/// ```swift
/// struct Article: SpotlightIndexable {
///     let id: UUID
///     let title: String
///     let body: String
///
///     var spotlightID: String { id.uuidString }
///     var spotlightTitle: String { title }
///     var spotlightDescription: String { String(body.prefix(300)) }
///     var spotlightKeywords: [String] { ["article"] }
///     var spotlightThumbnailData: Data? { nil }
///     var spotlightDomainIdentifier: String { "com.myapp.articles" }
/// }
/// ```
protocol SpotlightIndexable {
    /// Unique identifier for the searchable item.
    /// Must be stable across app launches (e.g., UUID string).
    var spotlightID: String { get }

    /// Title displayed in Spotlight results.
    var spotlightTitle: String { get }

    /// Description displayed below the title in Spotlight results.
    /// Keep under 300 characters for best display.
    var spotlightDescription: String { get }

    /// Keywords for matching search queries.
    /// Include synonyms and related terms the user might search for.
    var spotlightKeywords: [String] { get }

    /// Optional thumbnail image data (JPEG or PNG).
    /// Keep under 300x300 pixels for performance.
    var spotlightThumbnailData: Data? { get }

    /// Domain identifier for grouping related items.
    /// Enables bulk removal by domain (e.g., "com.myapp.articles").
    var spotlightDomainIdentifier: String { get }

    /// Optional: Content type for the attribute set.
    /// Defaults to general content. Override for specific types.
    var spotlightContentType: UTType { get }

    /// Optional: Expiration date after which the item is removed from the index.
    /// Defaults to nil (no expiration).
    var spotlightExpirationDate: Date? { get }
}

// MARK: - Defaults

extension SpotlightIndexable {
    var spotlightContentType: UTType { .content }
    var spotlightExpirationDate: Date? { nil }
    var spotlightThumbnailData: Data? { nil }
}

// MARK: - CSSearchableItem Conversion

extension SpotlightIndexable {
    /// Creates a `CSSearchableItem` from this model's Spotlight properties.
    func toSearchableItem() -> CSSearchableItem {
        let attributes = CSSearchableItemAttributeSet(contentType: spotlightContentType)
        attributes.title = spotlightTitle
        attributes.contentDescription = spotlightDescription
        attributes.keywords = spotlightKeywords
        attributes.thumbnailData = spotlightThumbnailData
        attributes.domainIdentifier = spotlightDomainIdentifier

        let item = CSSearchableItem(
            uniqueIdentifier: spotlightID,
            domainIdentifier: spotlightDomainIdentifier,
            attributeSet: attributes
        )

        item.expirationDate = spotlightExpirationDate

        return item
    }
}

SpotlightIndexManager.swift

import Foundation
import CoreSpotlight

/// Actor that manages all Core Spotlight indexing operations.
///
/// Wraps `CSSearchableIndex` with:
/// - Batch indexing for large datasets (configurable batch size)
/// - Safe concurrent access via actor isolation
/// - Domain-based bulk removal
/// - Full reindex support
///
/// Usage:
/// ```swift
/// // Index items
/// await SpotlightIndexManager.shared.index(items: articles)
///
/// // Remove specific items
/// await SpotlightIndexManager.shared.remove(identifiers: ["article-123"])
///
/// // Remove all items in a domain
/// await SpotlightIndexManager.shared.removeAll(domain: "com.myapp.articles")
///
/// // Full reindex
/// await SpotlightIndexManager.shared.reindexAll(items: articles, domain: "com.myapp.articles")
/// ```
actor SpotlightIndexManager {
    static let shared = SpotlightIndexManager()

    private let searchableIndex: CSSearchableIndex
    private let batchSize: Int

    init(
        index: CSSearchableIndex = .default(),
        batchSize: Int = 100
    ) {
        self.searchableIndex = index
        self.batchSize = batchSize
    }

    // MARK: - Index

    /// Index an array of `SpotlightIndexable` items.
    ///
    /// Automatically batches large datasets to avoid memory pressure.
    /// Each batch is indexed in sequence to respect system resources.
    func index<T: SpotlightIndexable>(items: [T]) async {
        guard !items.isEmpty else { return }

        let searchableItems = items.map { $0.toSearchableItem() }

        if searchableItems.count <= batchSize {
            await indexBatch(searchableItems)
        } else {
            // Chunk into batches
            for startIndex in stride(from: 0, to: searchableItems.count, by: batchSize) {
                let endIndex = min(startIndex + batchSize, searchableItems.count)
                let batch = Array(searchableItems[startIndex..<endIndex])
                await indexBatch(batch)
            }
        }
    }

    /// Index a single `SpotlightIndexable` item.
    func index<T: SpotlightIndexable>(item: T) async {
        await indexBatch([item.toSearchableItem()])
    }

    // MARK: - Remove

    /// Remove items by their unique identifiers.
    func remove(identifiers: [String]) async {
        guard !identifiers.isEmpty else { return }
        do {
            try await searchableIndex.deleteSearchableItems(withIdentifiers: identifiers)
        } catch {
            logError("Failed to remove items: \(error.localizedDescription)")
        }
    }

    /// Remove all items in a domain.
    ///
    /// Use this for bulk cleanup, e.g., removing all articles.
    func removeAll(domain: String) async {
        do {
            try await searchableIndex.deleteSearchableItems(withDomainIdentifiers: [domain])
        } catch {
            logError("Failed to remove domain '\(domain)': \(error.localizedDescription)")
        }
    }

    /// Remove all items from the app's Spotlight index.
    func removeAll() async {
        do {
            try await searchableIndex.deleteAllSearchableItems()
        } catch {
            logError("Failed to remove all items: \(error.localizedDescription)")
        }
    }

    // MARK: - Reindex

    /// Remove all items in a domain, then re-index with fresh data.
    ///
    /// Use after data migration or app update when indexed attributes change.
    func reindexAll<T: SpotlightIndexable>(items: [T], domain: String) async {
        await removeAll(domain: domain)
        await index(items: items)
    }

    // MARK: - Private

    private func indexBatch(_ items: [CSSearchableItem]) async {
        do {
            try await searchableIndex.indexSearchableItems(items)
        } catch {
            logError("Failed to index batch of \(items.count) items: \(error.localizedDescription)")
        }
    }

    private func logError(_ message: String) {
        #if DEBUG
        print("[SpotlightIndexManager] \(message)")
        #endif
    }
}

SpotlightAttributeBuilder.swift

import Foundation
import CoreSpotlight
import UniformTypeIdentifiers

/// Fluent builder for `CSSearchableItemAttributeSet`.
///
/// Provides a chainable API for constructing rich attribute sets
/// when you need more control than `SpotlightIndexable` provides.
///
/// Usage:
/// ```swift
/// let attributes = SpotlightAttributeBuilder(contentType: .text)
///     .title("SwiftUI Navigation Guide")
///     .description("Learn about NavigationStack and NavigationSplitView")
///     .keywords(["swiftui", "navigation", "ios"])
///     .thumbnail(imageData)
///     .url(URL(string: "myapp://articles/123")!)
///     .rating(4.5)
///     .build()
///
/// let item = CSSearchableItem(
///     uniqueIdentifier: "article-123",
///     domainIdentifier: "com.myapp.articles",
///     attributeSet: attributes
/// )
/// ```
struct SpotlightAttributeBuilder {
    private let attributeSet: CSSearchableItemAttributeSet

    init(contentType: UTType = .content) {
        self.attributeSet = CSSearchableItemAttributeSet(contentType: contentType)
    }

    // MARK: - Core Attributes

    /// Set the title displayed in Spotlight results.
    func title(_ value: String) -> SpotlightAttributeBuilder {
        attributeSet.title = value
        return self
    }

    /// Set the description displayed below the title.
    func description(_ value: String) -> SpotlightAttributeBuilder {
        attributeSet.contentDescription = value
        return self
    }

    /// Set keywords for search matching.
    func keywords(_ values: [String]) -> SpotlightAttributeBuilder {
        attributeSet.keywords = values
        return self
    }

    // MARK: - Visual Attributes

    /// Set thumbnail image data (JPEG or PNG, max 300x300 recommended).
    func thumbnail(_ data: Data?) -> SpotlightAttributeBuilder {
        attributeSet.thumbnailData = data
        return self
    }

    /// Set thumbnail from a URL (system fetches the image).
    func thumbnailURL(_ url: URL) -> SpotlightAttributeBuilder {
        attributeSet.thumbnailURL = url
        return self
    }

    // MARK: - Content Type Attributes

    /// Set a content URL for the item.
    func url(_ value: URL) -> SpotlightAttributeBuilder {
        attributeSet.url = value
        return self
    }

    /// Set the content type identifier.
    func contentType(_ type: UTType) -> SpotlightAttributeBuilder {
        attributeSet.contentType = type.identifier
        return self
    }

    // MARK: - Metadata Attributes

    /// Set a star rating (0.0 to 5.0, displayed as stars in Spotlight).
    func rating(_ value: Double) -> SpotlightAttributeBuilder {
        attributeSet.rating = NSNumber(value: value)
        return self
    }

    /// Set the content creation date.
    func creationDate(_ date: Date) -> SpotlightAttributeBuilder {
        attributeSet.contentCreationDate = date
        return self
    }

    /// Set the content modification date.
    func modificationDate(_ date: Date) -> SpotlightAttributeBuilder {
        attributeSet.contentModificationDate = date
        return self
    }

    /// Set the display name (alternative to title for file-like items).
    func displayName(_ value: String) -> SpotlightAttributeBuilder {
        attributeSet.displayName = value
        return self
    }

    /// Set the domain identifier.
    func domainIdentifier(_ value: String) -> SpotlightAttributeBuilder {
        attributeSet.domainIdentifier = value
        return self
    }

    // MARK: - People / Contact Attributes

    /// Set author names.
    func authors(_ values: [String]) -> SpotlightAttributeBuilder {
        attributeSet.authorNames = values
        return self
    }

    /// Set phone numbers (for contact-type items).
    func phoneNumbers(_ values: [String]) -> SpotlightAttributeBuilder {
        attributeSet.phoneNumbers = values
        return self
    }

    /// Set email addresses (for contact-type items).
    func emailAddresses(_ values: [String]) -> SpotlightAttributeBuilder {
        attributeSet.emailAddresses = values
        return self
    }

    // MARK: - Location Attributes

    /// Set latitude for location-based items.
    func latitude(_ value: Double) -> SpotlightAttributeBuilder {
        attributeSet.latitude = NSNumber(value: value)
        return self
    }

    /// Set longitude for location-based items.
    func longitude(_ value: Double) -> SpotlightAttributeBuilder {
        attributeSet.longitude = NSNumber(value: value)
        return self
    }

    /// Set named location (e.g., "San Francisco, CA").
    func namedLocation(_ value: String) -> SpotlightAttributeBuilder {
        attributeSet.namedLocation = value
        return self
    }

    // MARK: - Build

    /// Build and return the configured `CSSearchableItemAttributeSet`.
    func build() -> CSSearchableItemAttributeSet {
        attributeSet
    }
}

SpotlightSearchHandler.swift

import Foundation
import CoreSpotlight

/// Handles app continuation from Spotlight search result taps.
///
/// When a user taps a Spotlight result, the system delivers an
/// `NSUserActivity` with `activityType == CSSearchableItemActionType`.
/// This handler extracts the item identifier and routes to the
/// appropriate view.
///
/// ## SwiftUI Integration
///
/// ```swift
/// @main
/// struct MyApp: App {
///     @State private var navigationPath = NavigationPath()
///
///     var body: some Scene {
///         WindowGroup {
///             NavigationStack(path: $navigationPath) {
///                 ContentView()
///                     .navigationDestination(for: SpotlightDestination.self) { dest in
///                         DetailView(id: dest.itemID)
///                     }
///             }
///             .onContinueUserActivity(CSSearchableItemActionType) { activity in
///                 if let destination = SpotlightSearchHandler.shared.destination(from: activity) {
///                     navigationPath.append(destination)
///                 }
///             }
///         }
///     }
/// }
/// ```
///
/// ## UIKit Integration (SceneDelegate)
///
/// ```swift
/// func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
///     if let itemID = SpotlightSearchHandler.shared.extractItemID(from: userActivity) {
///         navigate(to: itemID)
///     }
/// }
/// ```
@Observable
final class SpotlightSearchHandler {
    static let shared = SpotlightSearchHandler()

    /// The most recently received item ID from a Spotlight tap.
    /// Observe this to react to Spotlight navigation.
    private(set) var pendingItemID: String?

    // MARK: - Extract

    /// Extract the searchable item identifier from a Spotlight user activity.
    ///
    /// Returns `nil` if the activity is not a Spotlight search continuation
    /// or if the identifier is missing.
    func extractItemID(from activity: NSUserActivity) -> String? {
        guard activity.activityType == CSSearchableItemActionType else { return nil }
        return activity.userInfo?[CSSearchableItemActivityIdentifier] as? String
    }

    /// Extract a query string if the user launched the app from Spotlight
    /// with a search query (iOS 16+).
    func extractQuery(from activity: NSUserActivity) -> String? {
        guard activity.activityType == CSQueryContinuationActionType else { return nil }
        return activity.userInfo?[CSSearchQueryString] as? String
    }

    // MARK: - Handle

    /// Handle a Spotlight continuation activity.
    ///
    /// Extracts the item ID and stores it as `pendingItemID` for observation.
    /// Returns the extracted item ID, or nil if not a valid Spotlight activity.
    @discardableResult
    func handle(_ activity: NSUserActivity) -> String? {
        if let itemID = extractItemID(from: activity) {
            pendingItemID = itemID
            return itemID
        }
        return nil
    }

    /// Clear the pending item after navigation is complete.
    func clearPending() {
        pendingItemID = nil
    }
}

// MARK: - Navigation Destination

/// A Hashable destination for use with `NavigationStack` and `.navigationDestination`.
struct SpotlightDestination: Hashable {
    let itemID: String
    let domain: String?

    init(itemID: String, domain: String? = nil) {
        self.itemID = itemID
        self.domain = domain
    }
}

extension SpotlightSearchHandler {
    /// Create a `SpotlightDestination` from a Spotlight user activity.
    ///
    /// Use with `NavigationStack`:
    /// ```swift
    /// .navigationDestination(for: SpotlightDestination.self) { dest in
    ///     resolveView(for: dest)
    /// }
    /// ```
    func destination(from activity: NSUserActivity) -> SpotlightDestination? {
        guard let itemID = extractItemID(from: activity) else { return nil }
        return SpotlightDestination(itemID: itemID)
    }
}

SpotlightSyncModifier.swift

import SwiftUI
import CoreSpotlight

/// ViewModifier that automatically indexes content when a view appears
/// and optionally deindexes when it disappears.
///
/// Usage:
/// ```swift
/// // Index when view appears
/// ArticleDetailView(article: article)
///     .spotlightIndexed(article)
///
/// // Index on appear, deindex on disappear
/// ArticleDetailView(article: article)
///     .spotlightIndexed(article, removeOnDisappear: true)
/// ```
struct SpotlightSyncModifier<T: SpotlightIndexable>: ViewModifier {
    let item: T
    let removeOnDisappear: Bool

    func body(content: Content) -> some View {
        content
            .task {
                await SpotlightIndexManager.shared.index(item: item)
            }
            .onDisappear {
                if removeOnDisappear {
                    Task {
                        await SpotlightIndexManager.shared.remove(identifiers: [item.spotlightID])
                    }
                }
            }
    }
}

extension View {
    /// Index this content in Spotlight when the view appears.
    ///
    /// - Parameters:
    ///   - item: The `SpotlightIndexable` item to index.
    ///   - removeOnDisappear: If `true`, removes the item from the index when the view disappears.
    ///     Defaults to `false` (items remain indexed after viewing).
    func spotlightIndexed<T: SpotlightIndexable>(
        _ item: T,
        removeOnDisappear: Bool = false
    ) -> some View {
        modifier(SpotlightSyncModifier(item: item, removeOnDisappear: removeOnDisappear))
    }
}

// MARK: - Siri Suggestions Activity Modifier

/// ViewModifier that creates an NSUserActivity for Siri suggestions.
///
/// Makes content eligible for Siri suggestions and Spotlight search
/// via the user activity system (complementary to CSSearchableItem indexing).
///
/// Usage:
/// ```swift
/// ArticleView(article: article)
///     .spotlightActivity(
///         id: article.id.uuidString,
///         title: article.title,
///         description: article.body.prefix(200),
///         keywords: article.tags,
///         eligibleForPrediction: true
///     )
/// ```
struct SpotlightActivityModifier: ViewModifier {
    let id: String
    let activityType: String
    let title: String
    let description: String?
    let keywords: Set<String>
    let eligibleForPrediction: Bool

    func body(content: Content) -> some View {
        content
            .userActivity(activityType) { activity in
                activity.title = title
                activity.isEligibleForSearch = true
                activity.isEligibleForPrediction = eligibleForPrediction
                activity.keywords = keywords
                activity.contentAttributeSet = {
                    let attributes = CSSearchableItemAttributeSet(contentType: .content)
                    attributes.title = title
                    attributes.contentDescription = description
                    attributes.keywords = Array(keywords)
                    attributes.relatedUniqueIdentifier = id
                    return attributes
                }()
            }
    }
}

extension View {
    /// Advertise this content as a Siri suggestion and Spotlight result
    /// via NSUserActivity.
    ///
    /// - Parameters:
    ///   - id: Unique identifier matching the CSSearchableItem's uniqueIdentifier.
    ///   - activityType: The activity type (use reverse-DNS, e.g., "com.myapp.viewArticle").
    ///   - title: Display title for the suggestion.
    ///   - description: Optional description text.
    ///   - keywords: Search keywords.
    ///   - eligibleForPrediction: Whether Siri can proactively suggest this content.
    func spotlightActivity(
        id: String,
        activityType: String = "com.myapp.viewItem",
        title: String,
        description: String? = nil,
        keywords: Set<String> = [],
        eligibleForPrediction: Bool = true
    ) -> some View {
        modifier(SpotlightActivityModifier(
            id: id,
            activityType: activityType,
            title: title,
            description: description,
            keywords: keywords,
            eligibleForPrediction: eligibleForPrediction
        ))
    }
}

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The 'generators' skill provides a robust set of templates and workflows for generating Swift code for iOS and macOS applications. Security analysis identified a surface for indirect prompt injection because the skill reads local project files to adapt its generation logic. It also utilizes dynamic execution by generating and running local Swift scripts to create application icons and uses shell commands to process images and audit project code. All external references are to well-known, established services.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    59/179 files flagged

  • ZeroLeaks5mo

    Scan incomplete

Signed by skilld at 30e898f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 2 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/generators