skills/generators/preview-data-generator/SKILL.md
Generate sample data and a multi-variant
npx skillsauth add rshankras/claude-code-apple-skills preview-data-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generates two tightly-coupled things for a SwiftUI view:
#Preview matrix — the variant blocks you'd otherwise hand-write to prototype and QA a view across light/dark, Dynamic Type, locale/RTL, device sizes, and data states.This is the design-time counterpart to testing/test-data-factory (which makes fixtures for the test suite). Where a factory already exists, this skill reuses Model.fixture() instead of inventing parallel data.
Use this skill when the user:
@Model and needs an in-memory seeded container for previewsPreviewModifierJust need fixtures for unit tests? Use testing/test-data-factory instead.
Want screenshot regression tests? Pair this with testing/snapshot-test-setup — the same data + variant matrix feeds snapshot tests.
Load both before generating:
| File | Purpose |
|------|---------|
| preview-data-patterns.md | Sample-data design, the edge-case catalog, SwiftData in-memory seeding, PreviewModifier (iOS 18), @Previewable, reusing test-data-factory |
| preview-matrix.md | The variant axes, preview traits:, .environment overrides, deployment-target fallbacks (#Preview vs PreviewProvider), data-state previews |
Generators are context-aware. Before writing code, detect:
| Check | How | Why it matters |
|-------|-----|----------------|
| Deployment target | Read project/.xcodeproj or Package.swift | iOS 17+ → #Preview macro; iOS 18+ → PreviewModifier + @Previewable; below 17 → PreviewProvider fallback |
| Target view + its models | Read the view file; Grep its init/properties for model types | Determines which types need sample data |
| Existing fixtures | Grep "static func fixture\|extension .*{ static (let\|var) preview" | Reuse Model.fixture() / existing .preview — never duplicate |
| SwiftData | Grep "@Model" on the model types | Use the in-memory .modelContainer(inMemory:) seed pattern, not plain structs |
| View shape | Does it take a model, a ViewModel, or fetch its own data? | Drives whether to inject data, a mock VM, or a seeded container |
| Platform | iOS / macOS / multiplatform | Device variants and some traits differ |
Ask via AskUserQuestion only what you can't infer — e.g. "Which states matter for this view: empty, loading, error, loaded, or all four?"
For each model the view needs, generate a Model.preview namespace with the realistic case and the edge cases (see preview-data-patterns.md for the full catalog):
extension Article {
/// A typical, realistic instance for the canvas.
static var preview: Article {
Article(id: UUID(), title: "Designing for the Smallest Screen",
author: "Mei Chen", body: String(repeating: "Lorem ipsum. ", count: 40),
imageURL: URL(string: "https://picsum.photos/seed/1/600/400"),
readMinutes: 6, isBookmarked: false)
}
/// Edge cases that expose layout bugs.
static var previewLongTitle: Article { .preview.with(title: "An Extraordinarily, Almost Unreasonably Long Headline That Wraps") }
static var previewNoImage: Article { .preview.with(imageURL: nil) }
static var previewList: [Article] { (1...12).map { .preview.with(id: UUID(), title: "Article \($0)") } }
static var previewEmpty: [Article] { [] }
}
If Article.fixture() already exists (from test-data-factory), build .preview on top of it rather than re-specifying every field.
Generate the #Preview blocks for the axes that matter (see preview-matrix.md). Always include data states — that's the UI-prototyping payoff:
#Preview("Loaded") { ArticleListView(state: .loaded(Article.previewList)) }
#Preview("Empty") { ArticleListView(state: .empty) }
#Preview("Loading") { ArticleListView(state: .loading) }
#Preview("Error") { ArticleListView(state: .error("No connection")) }
#Preview("Dark") { ArticleListView(state: .loaded(Article.previewList)).preferredColorScheme(.dark) }
#Preview("XXL Text") { ArticleListView(state: .loaded(Article.previewList)).dynamicTypeSize(.accessibility3) }
#Preview("German / RTL", traits: .sizeThatFitsLayout) {
ArticleDetailView(article: .previewLongTitle).environment(\.locale, .init(identifier: "de"))
}
Scale the matrix to the request — don't emit 20 previews for a label. A reasonable default per view: the relevant data states + dark mode + one large Dynamic Type + (if the view has text that localizes) one long-language/RTL check.
previewContainer seeded with sample models, attached via .modelContainer(previewContainer).PreviewModifier so the seeded container/data is built once and cached across every preview, applied with #Preview(traits: .modifier(SampleData())).@State/@Bindable → use @Previewable so state lives directly in the #Preview body.Article+Preview.swift (one file per model, in the model's group).ArticleListView+Previews.swift for large matrices. Ask if unsure.#if DEBUG so it never ships in release builds.After generating, always provide:
Article+Preview.swift, PreviewSupport.swift, etc.)#Preview blocks went, how to open the canvas#Preview / PreviewModifier / PreviewProvider)snapshot-test-setup✅ Generated previews for ArticleListView (iOS 18 target)
Files created:
• Article+Preview.swift — .preview, .previewLongTitle, .previewNoImage, .previewList, .previewEmpty
• PreviewSupport.swift — SampleData: PreviewModifier (cached in-memory container)
• ArticleListView+Previews.swift — 7 #Preview blocks (4 states, dark, XXL text, German)
Integration:
• Reused Article.fixture() from test-data-factory for base fields
• Open ArticleListView+Previews.swift → canvas shows all 7 variants
API used: #Preview + PreviewModifier (.modifier) — shared container built once, cached.
testing/test-data-factorytesting/snapshot-test-setup (feed it this data)#Preview {} is enoughgenerators/localization-setup already covers locale preview helpers; reuse themgenerators/persistence-setup → defines @Model types
↓
testing/test-data-factory → Model.fixture() for tests
↓
generators/preview-data-generator (THIS SKILL)
• reuses .fixture() for canvas data
• builds the #Preview state/appearance matrix
↓
testing/snapshot-test-setup → renders the same matrix for regression
.fixture()/.preview reused, not duplicated#Preview matrix covering the view's data states + relevant appearance axes#Preview / PreviewModifier / PreviewProvider)#if DEBUGGenerate the data and the variants together. A preview is only as useful as the data in it — and a view is only proven when you've seen it empty, overflowing, in the dark, and at AX5.
development
US web checkout via the StoreKit External Purchase Link entitlement — currently 0% Apple commission (litigation ongoing), how to ship it safely, and how to architect for a commission flip so a future ruling is a config change, not a rewrite. Use when adding external purchase links, weighing web checkout vs IAP, or planning US-storefront pricing strategy.
tools
Revenue beyond the single-app price tag — own-app bundles, Family Sharing as a conversion lever, cross-developer bundles & suites, and institutional licensing via Group Purchases / Apple School & Business Manager. Use when a developer has multiple apps, a subscription worth sharing, complementary indie partners, or school/clinic/business buyers.
testing
Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.
tools
Stage-by-stage audit of an app's App Store growth machinery against a 54-item P0–P9 playbook — every item scored from an App Store Connect MCP call, a codebase check, or an explicit question to the user, then routed to the skill or command that fixes it. Read-only on App Store Connect. Use for a growth audit or scorecard, a pre-launch growth plan, a quarterly re-audit, or "which growth levers am I missing."