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.

review-promptstorekit-patterns.md

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

StoreKit Review Patterns and Best Practices

Apple's Review Prompt Rules

System Limitations

  • Maximum 3 prompts per 365-day period per app
  • System decides whether to actually show the prompt
  • No feedback to app about whether prompt was shown
  • Prompt may not show in Simulator

Guidelines

  1. Don't prompt immediately - Wait for user to use app
  2. Don't interrupt - Choose natural break points
  3. After positive moments - User just accomplished something
  4. Never after negative - Errors, crashes, frustration
  5. Don't ask repeatedly - Respect the 3/year limit

Timing Strategies

Good Timing

  • After completing a task successfully
  • After achieving a milestone (10th entry, etc.)
  • After using the app for several sessions
  • At natural pause points (returning from background)

Bad Timing

  • On first launch
  • During active workflow
  • After an error
  • When user is trying to do something
  • Right after a purchase

Condition-Based Triggering

Session Count

// Don't ask until user has come back multiple times
func shouldPromptBasedOnSessions() -> Bool {
    sessionCount >= minimumSessions
}

Why: Users who return are more likely to be satisfied

Days Since Install

// Give users time to evaluate the app
func shouldPromptBasedOnDays() -> Bool {
    daysSinceInstall >= minimumDays
}

Why: First impressions may not reflect long-term value

Positive Actions

// Track successful completions
func shouldPromptBasedOnActions() -> Bool {
    positiveActionCount >= minimumPositiveActions
}

Why: Users who accomplish things are happier

Combined Conditions

// Require ALL conditions
func shouldPrompt() -> Bool {
    sessionCount >= minimumSessions &&
    daysSinceInstall >= minimumDays &&
    positiveActionCount >= minimumPositiveActions &&
    daysSinceLastPrompt >= cooldownDays
}

Cool-Down Strategy

Why Cool-Down Matters

  • Apple limits to 3/year anyway
  • Repeated prompts annoy users
  • Space out for maximum effectiveness

Recommended Cool-Down

// 60-90 days between prompts
let cooldownDays = 60

func canPromptAgain() -> Bool {
    guard let lastPromptDate = lastPromptDate else { return true }
    let daysSince = Calendar.current.dateComponents([.day], from: lastPromptDate, to: Date()).day ?? 0
    return daysSince >= cooldownDays
}

Platform-Specific Considerations

iOS

import StoreKit

func requestReview() {
    if let scene = UIApplication.shared.connectedScenes
        .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
        SKStoreReviewController.requestReview(in: scene)
    }
}

macOS (App Store Only)

import StoreKit

func requestReview() {
    // Check if running from App Store
    guard isAppStoreVersion() else {
        // Direct distribution - use alternative
        openAppStoreReviewPage()
        return
    }

    SKStoreReviewController.requestReview()
}

func isAppStoreVersion() -> Bool {
    // sandboxReceipt indicates development/TestFlight
    // Receipt present without sandbox indicates App Store
    guard let receiptURL = Bundle.main.appStoreReceiptURL else { return false }
    return FileManager.default.fileExists(atPath: receiptURL.path) &&
           !receiptURL.path.contains("sandboxReceipt")
}

macOS (Direct Distribution Alternative)

func openAppStoreReviewPage() {
    let appID = "YOUR_APP_ID"
    if let url = URL(string: "https://apps.apple.com/app/id\(appID)?action=write-review") {
        NSWorkspace.shared.open(url)
    }
}

Debug and Testing

Debug Override

#if DEBUG
static var debugAlwaysShow = false

func requestReviewIfAppropriate() {
    if Self.debugAlwaysShow {
        requestReview()
        return
    }
    // Normal logic...
}
#endif

State Reset

static func resetForTesting() {
    UserDefaults.standard.removeObject(forKey: Keys.sessionCount)
    UserDefaults.standard.removeObject(forKey: Keys.installDate)
    UserDefaults.standard.removeObject(forKey: Keys.lastPromptDate)
    UserDefaults.standard.removeObject(forKey: Keys.positiveActionCount)
}

Simulator Notes

  • Prompt may not appear in Simulator
  • Test on physical device for actual behavior
  • Use debug logging to verify conditions are met

Tracking What Worked

Analytics Integration

func requestReviewIfAppropriate() {
    guard shouldPrompt() else { return }

    // Track that we attempted to show prompt
    analytics.track(.reviewPromptAttempted(
        sessions: sessionCount,
        days: daysSinceInstall,
        actions: positiveActionCount
    ))

    requestReview()
    recordPromptDate()
}

A/B Testing Conditions

// Test different thresholds
let thresholds: ReviewThresholds = FeatureFlags.reviewPromptVariant == .aggressive
    ? .init(sessions: 2, days: 1, actions: 3)
    : .init(sessions: 5, days: 7, actions: 5)

In-App Feedback Alternative

For when App Store review isn't appropriate:

struct FeedbackView: View {
    @State private var rating: Int = 0
    @State private var feedback: String = ""

    var body: some View {
        VStack {
            Text("How are we doing?")

            // Star rating
            HStack {
                ForEach(1...5, id: \.self) { star in
                    Image(systemName: star <= rating ? "star.fill" : "star")
                        .onTapGesture { rating = star }
                }
            }

            if rating > 0 {
                if rating >= 4 {
                    // Happy user - ask for App Store review
                    Button("Rate on App Store") {
                        ReviewPromptManager.shared.requestReview()
                    }
                } else {
                    // Unhappy user - capture feedback internally
                    TextField("How can we improve?", text: $feedback)
                    Button("Send Feedback") {
                        sendFeedback(rating: rating, feedback: feedback)
                    }
                }
            }
        }
    }
}

Common Anti-Patterns

Don't Do This

// Bad: Prompting on every launch
func applicationDidBecomeActive() {
    SKStoreReviewController.requestReview()  // NO!
}

// Bad: Prompting after errors
func handleError(_ error: Error) {
    showErrorAlert(error)
    requestReview()  // NO! User is frustrated
}

// Bad: Prompting immediately after purchase
func handlePurchaseSuccess() {
    // User just paid - let them use what they bought
    requestReview()  // NO! Too early
}

// Bad: Begging
func showCustomPrompt() {
    "Please rate us 5 stars!"  // NO! Let the system prompt
}

Do This Instead

// Good: After positive action with conditions met
func taskCompleted() {
    saveTask()
    celebrateCompletion()

    // Check all conditions before prompting
    if shouldPromptForReview() {
        // Small delay to not interrupt celebration
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            requestReviewIfAppropriate()
        }
    }
}

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