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.

app-cliptemplates.md

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

App Clip Code Templates

Production-ready Swift templates for App Clip infrastructure. All code targets iOS 16+ and uses modern Swift concurrency. iOS 17+ required for @Observable.

AppClipApp.swift

import SwiftUI

/// @main entry point for the App Clip target.
///
/// Handles invocation from NFC tags, QR codes, Safari banners,
/// and Messages by listening for `NSUserActivityTypeBrowsingWeb`.
@main
struct AppClipApp: App {
    @State private var invocationHandler = InvocationHandler()

    var body: some Scene {
        WindowGroup {
            AppClipRootView(handler: invocationHandler)
                .onContinueUserActivity(
                    NSUserActivityTypeBrowsingWeb
                ) { userActivity in
                    guard let url = userActivity.webpageURL else { return }
                    invocationHandler.handleInvocation(url: url)
                }
        }
    }
}

/// Root view that routes to the appropriate experience based on invocation.
struct AppClipRootView: View {
    let handler: InvocationHandler

    var body: some View {
        Group {
            if let experience = handler.currentExperience {
                AnyView(experience.makeView())
            } else {
                DefaultAppClipView()
            }
        }
        .animation(.default, value: handler.currentExperience != nil)
    }
}

/// Default view shown when no specific invocation URL is provided.
struct DefaultAppClipView: View {
    var body: some View {
        ContentUnavailableView(
            "Welcome",
            systemImage: "app.badge",
            description: Text("Scan an NFC tag or QR code to get started.")
        )
    }
}

InvocationHandler.swift

import Foundation
import Observation

/// Parses App Clip invocation URLs and routes to the appropriate experience.
///
/// Registered URL patterns:
/// - `https://example.com/clip/order?location={id}` -> OrderExperience
/// - `https://example.com/clip/reserve?venue={id}` -> ReserveExperience
/// - `https://example.com/clip/checkin?event={id}` -> CheckInExperience
/// - `https://example.com/clip/product/{id}` -> PreviewContentExperience
@Observable
final class InvocationHandler {
    /// The currently active experience parsed from the invocation URL.
    private(set) var currentExperience: (any AppClipExperience)?

    /// The raw invocation URL, if any.
    private(set) var invocationURL: URL?

    /// Registered domain for App Clip invocations.
    /// Update this to match your associated domain.
    private let registeredDomain = "example.com"

    /// Handle an invocation URL from `onContinueUserActivity`.
    func handleInvocation(url: URL) {
        invocationURL = url
        currentExperience = parseURL(url)
    }

    /// Parse a URL into an App Clip experience.
    ///
    /// Returns `nil` if the URL doesn't match any registered pattern.
    func parseURL(_ url: URL) -> (any AppClipExperience)? {
        guard let host = url.host,
              host.contains(registeredDomain) else {
            return nil
        }

        let pathComponents = url.pathComponents.filter { $0 != "/" }
        let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?
            .queryItems ?? []

        guard pathComponents.first == "clip",
              pathComponents.count >= 2 else {
            return nil
        }

        let action = pathComponents[1]
        let parameters = Dictionary(
            uniqueKeysWithValues: queryItems.compactMap { item in
                item.value.map { (item.name, $0) }
            }
        )

        switch action {
        case "order":
            guard let locationID = parameters["location"] else { return nil }
            return OrderExperience(locationID: locationID)

        case "reserve":
            guard let venueID = parameters["venue"] else { return nil }
            return ReserveExperience(venueID: venueID)

        case "checkin":
            guard let eventID = parameters["event"] else { return nil }
            return CheckInExperience(eventID: eventID)

        case "product":
            guard pathComponents.count >= 3 else { return nil }
            let productID = pathComponents[2]
            return PreviewContentExperience(productID: productID)

        default:
            return nil
        }
    }
}

AppClipExperience.swift

import SwiftUI

/// Defines a single App Clip experience.
///
/// Each experience represents one user flow triggered by an invocation URL.
/// Implementations must provide a lightweight view that loads instantly
/// and delivers value without sign-in.
protocol AppClipExperience: Sendable {
    /// The type of experience for analytics and routing.
    var experienceType: AppClipExperienceType { get }

    /// Parameters extracted from the invocation URL.
    var parameters: [String: String] { get }

    /// Create the SwiftUI view for this experience.
    @MainActor func makeView() -> any View
}

/// Types of App Clip experiences.
enum AppClipExperienceType: String, Sendable {
    case orderFood
    case reserve
    case checkIn
    case previewContent
}

// MARK: - Order Experience

/// Handles food/drink ordering from a physical location.
///
/// Invocation: `https://example.com/clip/order?location={locationID}`
struct OrderExperience: AppClipExperience {
    let locationID: String

    var experienceType: AppClipExperienceType { .orderFood }

    var parameters: [String: String] {
        ["locationID": locationID]
    }

    @MainActor func makeView() -> any View {
        OrderExperienceView(locationID: locationID)
    }
}

struct OrderExperienceView: View {
    let locationID: String
    @State private var menuItems: [MenuItem] = []
    @State private var isLoading = true

    var body: some View {
        NavigationStack {
            Group {
                if isLoading {
                    ProgressView("Loading menu...")
                } else {
                    List(menuItems) { item in
                        MenuItemRow(item: item)
                    }
                }
            }
            .navigationTitle("Order")
            .task {
                await loadMenu()
            }
        }
    }

    private func loadMenu() async {
        // TODO: Replace with actual API call
        // Keep network requests minimal for App Clip size budget
        try? await Task.sleep(for: .milliseconds(500))
        menuItems = [
            MenuItem(id: "1", name: "Sample Item", price: 4.99)
        ]
        isLoading = false
    }
}

// MARK: - Reserve Experience

/// Handles reservation/booking at a venue.
///
/// Invocation: `https://example.com/clip/reserve?venue={venueID}`
struct ReserveExperience: AppClipExperience {
    let venueID: String

    var experienceType: AppClipExperienceType { .reserve }

    var parameters: [String: String] {
        ["venueID": venueID]
    }

    @MainActor func makeView() -> any View {
        ReserveExperienceView(venueID: venueID)
    }
}

struct ReserveExperienceView: View {
    let venueID: String
    @State private var selectedDate = Date()
    @State private var partySize = 2

    var body: some View {
        NavigationStack {
            Form {
                Section("Reservation Details") {
                    DatePicker("Date & Time", selection: $selectedDate,
                               in: Date()...,
                               displayedComponents: [.date, .hourAndMinute])

                    Stepper("Party size: \(partySize)", value: $partySize, in: 1...20)
                }

                Section {
                    Button("Reserve Now") {
                        // TODO: Submit reservation
                    }
                    .frame(maxWidth: .infinity)
                    .buttonStyle(.borderedProminent)
                }
            }
            .navigationTitle("Reserve")
        }
    }
}

// MARK: - Check-In Experience

/// Handles event or location check-in.
///
/// Invocation: `https://example.com/clip/checkin?event={eventID}`
struct CheckInExperience: AppClipExperience {
    let eventID: String

    var experienceType: AppClipExperienceType { .checkIn }

    var parameters: [String: String] {
        ["eventID": eventID]
    }

    @MainActor func makeView() -> any View {
        CheckInExperienceView(eventID: eventID)
    }
}

struct CheckInExperienceView: View {
    let eventID: String
    @State private var isCheckedIn = false

    var body: some View {
        NavigationStack {
            VStack(spacing: 24) {
                if isCheckedIn {
                    Image(systemName: "checkmark.circle.fill")
                        .font(.system(size: 80))
                        .foregroundStyle(.green)
                    Text("You're checked in!")
                        .font(.title)
                } else {
                    Image(systemName: "qrcode.viewfinder")
                        .font(.system(size: 80))
                        .foregroundStyle(.secondary)
                    Text("Ready to check in")
                        .font(.title)

                    Button("Check In Now") {
                        withAnimation {
                            isCheckedIn = true
                        }
                        // TODO: Submit check-in to server
                    }
                    .buttonStyle(.borderedProminent)
                    .controlSize(.large)
                }
            }
            .padding()
            .navigationTitle("Check In")
        }
    }
}

// MARK: - Preview Content Experience

/// Handles product or content preview.
///
/// Invocation: `https://example.com/clip/product/{productID}`
struct PreviewContentExperience: AppClipExperience {
    let productID: String

    var experienceType: AppClipExperienceType { .previewContent }

    var parameters: [String: String] {
        ["productID": productID]
    }

    @MainActor func makeView() -> any View {
        PreviewContentExperienceView(productID: productID)
    }
}

struct PreviewContentExperienceView: View {
    let productID: String
    @State private var product: ProductInfo?
    @State private var isLoading = true

    var body: some View {
        NavigationStack {
            Group {
                if isLoading {
                    ProgressView("Loading...")
                } else if let product {
                    ScrollView {
                        VStack(alignment: .leading, spacing: 16) {
                            // Product image placeholder
                            RoundedRectangle(cornerRadius: 12)
                                .fill(Color.secondary.opacity(0.2))
                                .aspectRatio(16 / 9, contentMode: .fit)
                                .overlay {
                                    Image(systemName: "photo")
                                        .font(.largeTitle)
                                        .foregroundStyle(.secondary)
                                }

                            Text(product.name)
                                .font(.title.bold())

                            Text(product.formattedPrice)
                                .font(.title2)
                                .foregroundStyle(.secondary)

                            Text(product.description)
                                .font(.body)

                            Button("Add to Cart") {
                                // TODO: Add to cart
                            }
                            .buttonStyle(.borderedProminent)
                            .controlSize(.large)
                            .frame(maxWidth: .infinity)
                        }
                        .padding()
                    }
                } else {
                    ContentUnavailableView(
                        "Product Not Found",
                        systemImage: "exclamationmark.triangle",
                        description: Text("This product is no longer available.")
                    )
                }
            }
            .navigationTitle("Product")
            .task {
                await loadProduct()
            }
        }
    }

    private func loadProduct() async {
        // TODO: Replace with actual API call
        try? await Task.sleep(for: .milliseconds(500))
        product = ProductInfo(
            id: productID,
            name: "Sample Product",
            price: 29.99,
            description: "Product description loaded from the server."
        )
        isLoading = false
    }
}

// MARK: - Supporting Models

struct MenuItem: Identifiable {
    let id: String
    let name: String
    let price: Double
}

struct MenuItemRow: View {
    let item: MenuItem

    var body: some View {
        HStack {
            Text(item.name)
            Spacer()
            Text(item.price, format: .currency(code: "USD"))
                .foregroundStyle(.secondary)
        }
    }
}

struct ProductInfo {
    let id: String
    let name: String
    let price: Double
    let description: String

    var formattedPrice: String {
        price.formatted(.currency(code: "USD"))
    }
}

LocationConfirmationView.swift

import SwiftUI
import CoreLocation

/// Verifies the user is physically at the expected location before
/// proceeding with the App Clip experience.
///
/// Uses the App Clip location confirmation API. The system shows a
/// confirmation dialog — the app never receives the exact location,
/// only a boolean result indicating if the user is within the expected region.
///
/// Usage:
/// ```swift
/// LocationConfirmationView(
///     region: CLCircularRegion(
///         center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
///         radius: 100,
///         identifier: "store-42"
///     )
/// ) {
///     // Proceed to experience
///     OrderExperienceView(locationID: "store-42")
/// }
/// ```
struct LocationConfirmationView<Content: View>: View {
    let region: CLCircularRegion
    @ViewBuilder let confirmedContent: () -> Content

    @State private var confirmationStatus: ConfirmationStatus = .pending
    @State private var locationManager = AppClipLocationManager()

    var body: some View {
        Group {
            switch confirmationStatus {
            case .pending:
                VStack(spacing: 20) {
                    ProgressView()
                        .controlSize(.large)
                    Text("Confirming your location...")
                        .font(.headline)
                    Text("This helps ensure you're at the right place.")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                        .multilineTextAlignment(.center)
                }
                .padding()

            case .confirmed:
                confirmedContent()

            case .denied:
                ContentUnavailableView(
                    "Location Not Confirmed",
                    systemImage: "location.slash",
                    description: Text(
                        "We couldn't confirm you're at the expected location. "
                        + "Please make sure you're at the right place and try again."
                    )
                )

            case .failed(let message):
                ContentUnavailableView(
                    "Location Error",
                    systemImage: "exclamationmark.triangle",
                    description: Text(message)
                )
            }
        }
        .task {
            await confirmLocation()
        }
    }

    private func confirmLocation() async {
        do {
            let confirmed = try await locationManager.confirmLocation(in: region)
            confirmationStatus = confirmed ? .confirmed : .denied
        } catch {
            confirmationStatus = .failed(error.localizedDescription)
        }
    }
}

// MARK: - Confirmation Status

enum ConfirmationStatus {
    case pending
    case confirmed
    case denied
    case failed(String)
}

// MARK: - Location Manager

/// Wraps CLLocationManager for App Clip location confirmation.
///
/// App Clips use a special confirmation flow where the system
/// shows a dialog to the user. The app receives only a boolean
/// result, never the precise location.
@Observable
final class AppClipLocationManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    private var continuation: CheckedContinuation<Bool, Error>?

    override init() {
        super.init()
        manager.delegate = self
    }

    /// Confirm the user is within the specified region.
    ///
    /// - Parameter region: The expected location region.
    /// - Returns: `true` if the user confirmed they are at the location.
    func confirmLocation(in region: CLCircularRegion) async throws -> Bool {
        return try await withCheckedThrowingContinuation { continuation in
            self.continuation = continuation

            // Request location confirmation — system shows a dialog
            manager.requestWhenInUseAuthorization()
            manager.startMonitoring(for: region)
            manager.requestState(for: region)
        }
    }

    // MARK: - CLLocationManagerDelegate

    func locationManager(
        _ manager: CLLocationManager,
        didDetermineState state: CLRegionState,
        for region: CLRegion
    ) {
        manager.stopMonitoring(for: region)

        switch state {
        case .inside:
            continuation?.resume(returning: true)
        case .outside, .unknown:
            continuation?.resume(returning: false)
        @unknown default:
            continuation?.resume(returning: false)
        }
        continuation = nil
    }

    func locationManager(
        _ manager: CLLocationManager,
        monitoringDidFailFor region: CLRegion?,
        withError error: Error
    ) {
        if let region {
            manager.stopMonitoring(for: region)
        }
        continuation?.resume(throwing: error)
        continuation = nil
    }
}

FullAppUpgradeView.swift

import SwiftUI
import StoreKit

/// Presents an SKOverlay banner prompting the user to download the full app.
///
/// Shows a list of benefits the user will get by upgrading,
/// and displays the system App Store overlay for one-tap install.
///
/// Usage:
/// ```swift
/// FullAppUpgradeView(
///     appStoreID: "123456789",
///     benefits: [
///         "Order history and favorites",
///         "Loyalty rewards program",
///         "Push notifications for order updates"
///     ]
/// )
/// ```
struct FullAppUpgradeView: View {
    let appStoreID: String
    let benefits: [String]

    @State private var showOverlay = false
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack(spacing: 24) {
            // Header
            VStack(spacing: 8) {
                Image(systemName: "arrow.down.app.fill")
                    .font(.system(size: 56))
                    .foregroundStyle(.tint)

                Text("Get the Full App")
                    .font(.title.bold())

                Text("Unlock all features with the full app.")
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            // Benefits list
            VStack(alignment: .leading, spacing: 12) {
                ForEach(benefits, id: \.self) { benefit in
                    HStack(spacing: 12) {
                        Image(systemName: "checkmark.circle.fill")
                            .foregroundStyle(.green)
                        Text(benefit)
                            .font(.body)
                    }
                }
            }
            .padding()
            .background {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.secondary.opacity(0.1))
            }

            Spacer()

            // Install button
            Button {
                showOverlay = true
            } label: {
                Text("Download Full App")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.large)

            // Skip button
            Button("Not Now") {
                dismiss()
            }
            .foregroundStyle(.secondary)
        }
        .padding()
        .appStoreOverlay(isPresented: $showOverlay) {
            SKOverlay.AppClipConfiguration(position: .bottom)
        }
    }
}

// MARK: - Inline Upgrade Banner

/// A compact banner view that can be placed at the bottom of any experience
/// to suggest upgrading to the full app.
///
/// Usage:
/// ```swift
/// VStack {
///     // Main experience content
///     OrderExperienceView(locationID: locationID)
///
///     UpgradeBanner(appStoreID: "123456789")
/// }
/// ```
struct UpgradeBanner: View {
    let appStoreID: String
    @State private var showOverlay = false

    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 2) {
                Text("Get the full experience")
                    .font(.subheadline.bold())
                Text("Download the app for all features")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            Button("Get") {
                showOverlay = true
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.small)
        }
        .padding()
        .background {
            RoundedRectangle(cornerRadius: 12)
                .fill(Color.secondary.opacity(0.1))
        }
        .padding(.horizontal)
        .appStoreOverlay(isPresented: $showOverlay) {
            SKOverlay.AppClipConfiguration(position: .bottom)
        }
    }
}

SharedDataManager.swift

import Foundation

/// Manages shared data between the App Clip and the full app via App Group.
///
/// Data stored by the App Clip can be read by the full app after install,
/// enabling seamless transfer of user activity (orders, preferences, etc.).
///
/// **Important:** App Clip data is deleted after 8 hours of inactivity.
/// Use this manager to persist critical data in the shared App Group container
/// so the full app can access it after install.
///
/// Setup:
/// 1. Add App Group capability to both targets
/// 2. Use the same group identifier (e.g., `group.com.yourapp`)
///
/// Usage:
/// ```swift
/// // App Clip: Save data
/// SharedDataManager.shared.save(order, forKey: "pendingOrder")
///
/// // Full App: Load data
/// if let order: Order = SharedDataManager.shared.load(forKey: "pendingOrder") {
///     showPendingOrder(order)
/// }
/// ```
final class SharedDataManager: Sendable {
    /// Shared instance using the default App Group.
    /// Update the suite name to match your App Group identifier.
    static let shared = SharedDataManager(suiteName: "group.com.yourapp")

    private let defaults: UserDefaults?

    /// Initialize with an App Group suite name.
    ///
    /// - Parameter suiteName: The App Group identifier (e.g., `group.com.yourapp`).
    init(suiteName: String) {
        self.defaults = UserDefaults(suiteName: suiteName)
    }

    // MARK: - Save

    /// Save a Codable value to the shared container.
    ///
    /// - Parameters:
    ///   - value: The value to save.
    ///   - key: The key to store the value under.
    func save<T: Codable>(_ value: T, forKey key: String) {
        guard let data = try? JSONEncoder().encode(value) else { return }
        defaults?.set(data, forKey: key)

        // Also store a timestamp for migration awareness
        defaults?.set(Date(), forKey: "\(key)_timestamp")
    }

    // MARK: - Load

    /// Load a Codable value from the shared container.
    ///
    /// - Parameter key: The key the value was stored under.
    /// - Returns: The decoded value, or `nil` if not found or decoding fails.
    func load<T: Codable>(forKey key: String) -> T? {
        guard let data = defaults?.data(forKey: key) else { return nil }
        return try? JSONDecoder().decode(T.self, from: data)
    }

    // MARK: - Timestamp

    /// Get the timestamp when a value was saved.
    ///
    /// Useful to check if shared data is stale or fresh.
    func timestamp(forKey key: String) -> Date? {
        defaults?.object(forKey: "\(key)_timestamp") as? Date
    }

    // MARK: - Remove

    /// Remove a value from the shared container.
    func remove(forKey key: String) {
        defaults?.removeObject(forKey: key)
        defaults?.removeObject(forKey: "\(key)_timestamp")
    }

    // MARK: - Migration

    /// Check if there is pending data from the App Clip to migrate.
    ///
    /// Call this in the full app's launch sequence to detect
    /// and import App Clip data.
    ///
    /// - Parameter keys: Keys to check for pending data.
    /// - Returns: Keys that have data available.
    func pendingMigrationKeys(from keys: [String]) -> [String] {
        keys.filter { defaults?.data(forKey: $0) != nil }
    }

    /// Remove all migrated data after successful import.
    ///
    /// Call this after the full app has imported all App Clip data.
    func clearMigratedData(keys: [String]) {
        for key in keys {
            remove(forKey: key)
        }
    }
}

// MARK: - Migration Helper

/// Handles one-time data migration from App Clip to full app.
///
/// Usage in the full app's root view or App struct:
/// ```swift
/// .task {
///     AppClipMigrator.migrateIfNeeded { migrated in
///         if migrated.contains("pendingOrder") {
///             // Show the user their pending order from the App Clip
///         }
///     }
/// }
/// ```
enum AppClipMigrator {
    private static let migrationCompleteKey = "appClipMigrationComplete"

    /// Known keys that the App Clip may have stored.
    /// Update this list to match your App Clip's stored data keys.
    static let knownKeys = [
        "pendingOrder",
        "userPreferences",
        "recentActivity"
    ]

    /// Check for and migrate App Clip data.
    ///
    /// This is idempotent — it only runs once per install.
    ///
    /// - Parameter handler: Closure called with the list of keys that had data.
    static func migrateIfNeeded(handler: ([String]) -> Void) {
        let manager = SharedDataManager.shared

        // Skip if already migrated
        guard !(UserDefaults.standard.bool(forKey: migrationCompleteKey)) else {
            return
        }

        let pendingKeys = manager.pendingMigrationKeys(from: knownKeys)

        if !pendingKeys.isEmpty {
            handler(pendingKeys)

            // Clean up after migration
            manager.clearMigratedData(keys: pendingKeys)
        }

        UserDefaults.standard.set(true, forKey: migrationCompleteKey)
    }
}

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