ios-dev-agent / rules
Cursor rule4 starsChanged 6 months agoMITView on GitHub
iOS development rule
Where files are headingNew Cursor rule files have more rules about planning and tracking tasks, and fewer about code style.
Of the Cursor rule files we read, the 4,183 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository76 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .cursor/rules/components.mdcCursor rule
- .cursor/rules/currency.mdcCursor rule
- .cursor/rules/file-structure.mdcCursor rule
- .cursor/rules/forbidden-patterns.mdcCursor rule
- .cursor/rules/mvvm-architecture.mdcCursor rule
- .cursor/rules/pricing-display.mdcCursor rule
- .cursor/rules/scope.mdcCursor rule
- .cursor/rules/swift-conventions.mdcCursor rule
- accessibilitySkill
- animationsSkill
- apple-developer-authSkill
- apple-signinSkill
- app-store-preflightSkill
- asc-app-create-uiSkill
- asc-aso-auditSkill
- asc-build-lifecycleSkill
- asc-cli-usageSkill
- asc-crash-triageSkill
- asc-id-resolverSkill
- asc-localize-metadataSkill
- asc-metadata-syncSkill
- asc-metadata-yamlSkill
- asc-notarizationSkill
- asc-ppp-pricingSkill
- asc-release-flowSkill
- asc-revenuecat-catalog-syncSkill
- asc-screenshot-resizeSkill
- asc-shots-pipelineSkill
- asc-signing-setupSkill
- ascSkill
- asc-submission-healthSkill
- asc-subscription-localizationSkill
- asc-testflight-orchestrationSkill
- asc-wall-submitSkill
- asc-whats-new-writerSkill
- asc-workflowSkill
- asc-xcode-buildSkill
- billing-finance-calculatorSkill
- buildSkill
- business-modelSkill
- chartsSkill
- consolidate-errorsSkill
- content-composerSkill
- crashSkill
- debuggingSkill
- feature-directory-structureSkill
- feedback-statesSkill
- fix-errorSkill
- formsSkill
- foundation-modelsSkill
- hapticsSkill
- healthkitSkill
- layoutSkill
- listsSkill
- navigationSkill
- notificationsSkill
- onboarding-questionnaireSkill
- onboardingSkill
- The first 60 shown.
The file
Show its rules and text
We split this design-system.mdc into the rules it gives an agent
28 rules. About 0 are common, 10 are distinctive, and 18 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
18
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 11
Every color, font, and spacing value in the app MUST come from AppTheme.
- Distinctive: line 13
Before writing ANY view code, verify:
- Very specific: line 62
Every app MUST use a centralized theme with nested enums for Colors, Fonts, and Spacing.
- Very specific: line 134
NEVER: use raw .font(.title2) or .font(.headline) in views — always AppTheme.Fonts.title2
- Very specific: line 135
NEVER: use .font(.system(size: N)) inline — it opts out of Dynamic Type and violates the token rule
- Very specific: line 136
When SF Symbol icons need a specific size that doesn't match a text style, create a named token in AppTheme.Fonts (e.g.
- Distinctive: line 137
No custom fonts, no downloaded fonts
- Distinctive: line 141
Every AppTheme MUST define text color tokens using UIKit's adaptive system colors.
- Very specific: line 167
Use .symbolRenderingMode(.hierarchical) or .symbolRenderingMode(.palette) for visual depth
- Distinctive: line 168
No custom icon assets unless the app concept specifically requires them
- Very specific: line 171
Every app MUST define a Color(hex:) initializer in AppTheme.swift so palette hex values can be used:
- Very specific: line 189
When the app has appearance switching (dark/light/system), also define:
- Very specific: line 234
When the app does NOT support both light and dark appearances (no dark-mode rule key), the pipeline automatically locks appearance via …
- Very specific: line 238
dark-moderule key: No lock — the app supports adaptive light/dark/system via a user preference picker. - Distinctive: line 239
visionOS: No appearance lock needed — visionOS has no dark mode.
- Distinctive: line 240
macOS: No appearance lock. macOS apps always follow the system appearance (dark/light).
- Distinctive: line 252
Do NOT add dark mode support, colorScheme checks, or custom dark/light color handling unless the user explicitly requests it
- Very specific: line 253
NEVER use hardcoded SwiftUI colors like .blue, .orange, .white, .black directly in views
- Distinctive: line 254
Keep brand/surface tokens explicit in AppTheme so appearance changes do not shift core palette identity
- Very specific: line 266
Use AppTheme.Spacing constants throughout — never raw numeric values
- Very specific: line 269
Every list or collection MUST have an empty state.
- Very specific: line 291
Always specify presentationDetents on .sheet:
- Very specific: line 295
Use .sheet / .fullScreenCover for creation forms
- Distinctive: line 298
Use subtle, purposeful animations for state changes and list mutations:
- Very specific: line 311
Always: use withAnimation(.spring) for toggle/complete state changes
- Very specific: line 312
Always: add .transition(.opacity.combined(with: .scale)) for list add/remove
- Distinctive: line 313
Never: add gratuitous motion that slows down interaction
- Very specific: line 314
Keep animations subtle — .spring and .default curves only
≈ “Common” is an estimate: at least 1 in 5 of the 6,668 Cursor rule files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
---
description: iOS development rule
globs: "**/*.swift"
alwaysApply: true
---
# Design System Rules
## MANDATORY — AppTheme Is the Single Source of Truth
Every color, font, and spacing value in the app MUST come from `AppTheme`. No exceptions.
Before writing ANY view code, verify:
1. Does `AppTheme.Colors` have a token for the color I need? If not, add one.
2. Does `AppTheme.Fonts` have a token for the font I need? If not, add one.
3. Am I using `AppTheme.Spacing` for padding/spacing? If not, switch to it.
## FORBIDDEN — Hardcoded Styling (Zero Tolerance)
These patterns are **BANNED** everywhere in feature views. Violations MUST be caught and fixed.
```swift
// BANNED — hardcoded colors
.foregroundStyle(.white) // use AppTheme.Colors.textPrimary
.foregroundStyle(.white.opacity(0.8)) // use AppTheme.Colors.textSecondary
.foregroundStyle(.white.opacity(0.6)) // use AppTheme.Colors.textTertiary
.foregroundStyle(.black) // use AppTheme.Colors.textPrimary
.foregroundStyle(Color.red) // define AppTheme.Colors.error or semantic token
.foregroundStyle(Color.blue) // define AppTheme.Colors.accent or semantic token
.background(.blue) // use AppTheme.Colors.* token
.background(Color(hex: "FF0000")) // define in AppTheme.Colors, reference the token
.tint(.white) // use AppTheme.Colors.* token
// BANNED — hardcoded fonts
.font(.system(size: 48)) // use AppTheme.Fonts.* token
.font(.system(size: 64)) // use AppTheme.Fonts.* token
.font(.system(.largeTitle, design: .rounded, weight: .bold)) // define in AppTheme.Fonts
.font(.title2) // use AppTheme.Fonts.title2
.font(.caption) // use AppTheme.Fonts.caption
.font(.headline) // use AppTheme.Fonts.headline
// BANNED — hardcoded spacing
.padding(20) // use AppTheme.Spacing.*
.padding(.horizontal, 12) // use AppTheme.Spacing.*
VStack(spacing: 10) // use AppTheme.Spacing.*
```
```swift
// CORRECT — always use AppTheme tokens
.foregroundStyle(AppTheme.Colors.textPrimary) // system adaptive — auto-adapts to appearance
.foregroundStyle(AppTheme.Colors.textSecondary)
.font(AppTheme.Fonts.title2)
.font(AppTheme.Fonts.caption)
.padding(AppTheme.Spacing.md)
.padding(.horizontal, AppTheme.Spacing.sm)
VStack(spacing: AppTheme.Spacing.sm)
.background(AppTheme.Colors.background) // custom palette hex — brand identity
```
## AppTheme Pattern
Every app **MUST** use a centralized theme with **nested enums** for `Colors`, `Fonts`, and `Spacing`. Do NOT use a flat enum with top-level static properties.
```swift
// REQUIRED — always use nested enums
import SwiftUI
enum AppTheme {
enum Colors {
// Brand/theme colors — custom hex from palette
static let primary = Color(hex: "...")
static let secondary = Color(hex: "...")
static let accent = Color(hex: "...")
static let background = Color(hex: "...")
static let surface = Color(hex: "...")
// Text colors — REQUIRED — use UIKit adaptive colors
// These auto-adapt to the locked appearance (Dark or Light).
static let textPrimary = Color(.label)
static let textSecondary = Color(.secondaryLabel)
static let textTertiary = Color(.tertiaryLabel)
}
enum Fonts {
static let largeTitle = Font.system(.largeTitle, design: .rounded, weight: .bold)
static let title = Font.system(.title, design: .rounded, weight: .bold)
static let title2 = Font.system(.title2, design: .rounded, weight: .semibold)
static let title3 = Font.system(.title3, design: .rounded, weight: .semibold)
static let headline = Font.system(.headline, design: .rounded)
static let body = Font.system(.body, design: .rounded)
static let callout = Font.system(.callout, design: .rounded)
static let subheadline = Font.system(.subheadline, design: .rounded)
static let footnote = Font.system(.footnote, design: .rounded)
static let caption = Font.system(.caption, design: .rounded)
static let caption2 = Font.system(.caption2, design: .rounded)
// Icon-specific sizes — use when SF Symbols need a size that doesn't match a text style
static let heroIcon = Font.system(size: 80, weight: .regular, design: .rounded)
static let bulletIcon = Font.system(size: 6, weight: .regular, design: .rounded)
}
enum Spacing {
static let xs: CGFloat = 4
static let sm: CGFloat = 8
static let md: CGFloat = 16
static let lg: CGFloat = 24
static let xl: CGFloat = 40
}
enum Style {
static let cornerRadius: CGFloat = 12
static let cardCornerRadius: CGFloat = 16
}
}
```
```swift
// FORBIDDEN — never use flat structure
enum AppTheme {
static let accentColor = Color.blue // wrong
static let spacing: CGFloat = 8 // wrong
}
```
Reference as: `AppTheme.Colors.accent`, `AppTheme.Fonts.headline`, `AppTheme.Spacing.md`
## Fonts — AppTheme.Fonts Required
The `Fonts` enum MUST exist in every AppTheme. It defines the app's typography tokens using the font design from the plan.
Rules:
- **System fonts only** — use SwiftUI font styles: `.largeTitle`, `.title`, `.headline`, `.body`, `.caption`
- Apply the plan's `fontDesign` (rounded, serif, monospaced, default) via `Font.system(.style, design: .rounded)`
- **NEVER** use raw `.font(.title2)` or `.font(.headline)` in views — always `AppTheme.Fonts.title2`
- **NEVER** use `.font(.system(size: N))` inline — it opts out of Dynamic Type and violates the token rule
- When SF Symbol icons need a specific size that doesn't match a text style, create a named token in `AppTheme.Fonts` (e.g. `heroIcon`, `bulletIcon`) — never use `.font(.system(size: N))` inline
- No custom fonts, no downloaded fonts
## Text Colors — System Adaptive via UIKit
Every AppTheme MUST define text color tokens using UIKit's adaptive system colors. These automatically adapt to the locked appearance mode (Dark or Light) set via Info.plist, so text is always legible against the palette background.
```swift
// REQUIRED — UIKit adaptive text colors
static let textPrimary = Color(.label) // white in Dark, black in Light
static let textSecondary = Color(.secondaryLabel) // adapts opacity per appearance
static let textTertiary = Color(.tertiaryLabel) // adapts opacity per appearance
```
**Do NOT use static colors for text** — no `Color.white`, `Color.black`, `Color.primary`, or `.opacity()` on raw colors. The appearance lock ensures UIKit adaptive colors pick the correct variant automatically.
| Instead of | Use |
|---|---|
| `.foregroundStyle(.white)` | `AppTheme.Colors.textPrimary` |
| `.foregroundStyle(.black)` | `AppTheme.Colors.textPrimary` |
| `.foregroundStyle(.white.opacity(0.8))` | `AppTheme.Colors.textSecondary` |
| `.foregroundStyle(.white.opacity(0.6))` | `AppTheme.Colors.textTertiary` |
| `.foregroundStyle(.secondary)` | `AppTheme.Colors.textSecondary` |
| `.foregroundStyle(Color.primary)` | `AppTheme.Colors.textPrimary` |
**macOS note**: On macOS, use `Color(.labelColor)`, `Color(.secondaryLabelColor)`, `Color(.tertiaryLabelColor)` instead — AppKit uses different names than UIKit. For multi-platform shared code, use `#if canImport(UIKit)` / `#if canImport(AppKit)` to select the correct initializer.
## Icons (SF Symbols)
- **SF Symbols only** for all icons — required for every list row, button, empty state, and tab
- Reference via `Image(systemName: "symbol.name")`
- Pick domain-appropriate symbols (e.g. "checkmark.circle.fill" for todos, "note.text" for notes, "heart.fill" for favorites)
- Use `.symbolRenderingMode(.hierarchical)` or `.symbolRenderingMode(.palette)` for visual depth
- No custom icon assets unless the app concept specifically requires them
## Color(hex:) Extension
Every app MUST define a `Color(hex:)` initializer in AppTheme.swift so palette hex values can be used:
```swift
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: .init(charactersIn: "#"))
let scanner = Scanner(string: hex)
var rgbValue: UInt64 = 0
scanner.scanHexInt64(&rgbValue)
self.init(
red: Double((rgbValue & 0xFF0000) >> 16) / 255.0,
green: Double((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: Double(rgbValue & 0x0000FF) / 255.0
)
}
}
```
When the app has appearance switching (dark/light/system), also define:
**iOS / tvOS / visionOS** (UIKit available):
```swift
extension Color {
init(light: String, dark: String) {
self.init(uiColor: UIColor { traits in
traits.userInterfaceStyle == .dark ? UIColor(Color(hex: dark)) : UIColor(Color(hex: light))
})
}
}
```
**macOS** (AppKit, no UIKit):
```swift
extension Color {
init(light: String, dark: String) {
self.init(nsColor: NSColor(name: nil, dynamicProvider: { appearance in
let isDark = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
return isDark ? NSColor(Color(hex: dark)) : NSColor(Color(hex: light))
}))
}
}
```
**Multi-platform shared code** — use `#if canImport`:
```swift
extension Color {
init(light: String, dark: String) {
#if canImport(UIKit)
self.init(uiColor: UIColor { traits in
traits.userInterfaceStyle == .dark ? UIColor(Color(hex: dark)) : UIColor(Color(hex: light))
})
#elseif canImport(AppKit)
self.init(nsColor: NSColor(name: nil, dynamicProvider: { appearance in
let isDark = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
return isDark ? NSColor(Color(hex: dark)) : NSColor(Color(hex: light))
}))
#endif
}
}
```
## Appearance Mode — Palette-Aware Lock via Info.plist
When the app does NOT support both light and dark appearances (no `dark-mode` rule key), the pipeline automatically locks appearance via Info.plist based on palette brightness:
- **Dark palette** (background brightness < 128): `UIUserInterfaceStyle` is set to `Dark`. System chrome (status bar, navigation bars, alerts, sheets, pickers) renders in dark mode to match the app's dark background.
- **Light palette** (background brightness >= 128 or no palette): `UIUserInterfaceStyle` is set to `Light`.
- **`dark-mode` rule key**: No lock — the app supports adaptive light/dark/system via a user preference picker.
- **visionOS**: No appearance lock needed — visionOS has no dark mode. The glass material auto-adapts to the physical environment.
- **macOS**: No appearance lock. macOS apps always follow the system appearance (dark/light). Users expect Mac apps to respect their system preference.
This is handled at the XcodeGen project generation level — **do NOT use `.preferredColorScheme()` for this purpose**. The Info.plist approach ensures the entire app (including system chrome, alerts, and sheets) respects the locked appearance, not just SwiftUI views.
**Text colors auto-adapt**: Because the appearance is locked at the system level, UIKit adaptive colors (`Color(.label)`, `Color(.secondaryLabel)`, `Color(.tertiaryLabel)`) automatically pick the correct variant. Dark-locked apps get white text; light-locked apps get black text. No manual color switching needed.
**When the app supports dark mode** (`dark-mode` in rule_keys with `Color(light:dark:)` adaptive tokens), the pipeline omits these keys and the app follows the system appearance.
## Colors
- **Brand/theme tokens** (primary, secondary, accent, background, surface) use `Color(hex:)` with palette values — these are the app's visual identity
- **Text tokens** (textPrimary, textSecondary, textTertiary) use UIKit adaptive colors (`Color(.label)`, etc.) — these auto-adapt to the appearance lock
- **One accent color** that fits the app's purpose
- Do NOT add dark mode support, colorScheme checks, or custom dark/light color handling unless the user explicitly requests it
- NEVER use hardcoded SwiftUI colors like `.blue`, `.orange`, `.white`, `.black` directly in views
- Keep brand/surface tokens explicit in AppTheme so appearance changes do not shift core palette identity
### Platform-Specific Color Rules
- **visionOS**: AppTheme colors are ONLY for accent buttons, badges, and small decorative elements. NEVER use AppTheme.Colors for backgrounds or body text. Use system glass and vibrancy instead.
- **tvOS**: AppTheme palette must use muted, desaturated colors. Saturation is overwhelming on large TV screens. Dark-first design — light text on dark backgrounds.
- **macOS**: Standard color usage similar to iOS. Sidebar icons should be monochrome (system handles tinting).
- **iOS**: Full AppTheme color palette usage is appropriate.
## Spacing Standards
- **16pt** standard padding (outer margins, section spacing)
- **8pt** compact spacing (between related elements)
- **24pt** large spacing (between major sections)
- Use `AppTheme.Spacing` constants throughout — never raw numeric values
## Empty States
Every list or collection MUST have an empty state. Use `ContentUnavailableView` (iOS 17+) for a polished look:
```swift
if items.isEmpty {
ContentUnavailableView(
"No Notes Yet",
systemImage: "note.text",
description: Text("Tap + to create your first note")
)
} else {
// Show the list
}
```
## Surface Materials
Map the design `surfaces` token to SwiftUI materials:
- **glass** -> `.ultraThinMaterial` (modern/translucent)
- **material** -> `.regularMaterial` (depth/layers)
- **solid** -> opaque `Color` from palette (clean/opaque)
- **flat** -> no shadows, no materials (minimal)
## Sheet Sizing
Always specify `presentationDetents` on `.sheet`:
- Small option pickers -> `.height(N)` (calculate based on content)
- Medium forms -> `.medium`
- Complex multi-section -> `.large`
- Use `.sheet` / `.fullScreenCover` for creation forms
## Animations
Use subtle, purposeful animations for state changes and list mutations:
```swift
withAnimation(.spring) {
item.isComplete.toggle()
}
.transition(.opacity.combined(with: .scale))
.contentTransition(.numericText())
.animation(.default, value: selectedFilter)
```
Rules:
- **Always** use `withAnimation(.spring)` for toggle/complete state changes
- **Always** add `.transition(.opacity.combined(with: .scale))` for list add/remove
- **Never** add gratuitous motion that slows down interaction
- Keep animations subtle — `.spring` and `.default` curves only
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

