cursor / claude rules

SwiftUI Guidelines Rules

Cursor rules for SwiftUI on iOS 15+ with Swift 6 strict concurrency. MVVM/Clean Architecture, body size limit, Sendable and MainActor, LazyVStack, async/await over onAppear, AsyncImage, and SwiftUI performance traps.
by community updated Sep 10, 2026 other

## what it does

tl;dr: pull swiftui-guidelines-cursorrules-prompt-file.mdc from awesome-cursorrules before any SwiftUI project in Cursor. Models default to UIKit patterns, put networking in body, and miss Swift 6’s Sendable/MainActor split — every one of those is a warning-as-error in modern projects.

What it does

This rule set configures Cursor to write SwiftUI as a Senior iOS Engineer would: Swift 6.0+ with strict concurrency, MVVM with Coordinators, a 50-line body ceiling, lazy stacks with stable ids, MainActor ViewModels, actor-isolated services, and #Preview for every screen. Without it, models reach for GeometryReader, Timer, and .onAppear with a fetched Task that nobody cancels.

Sourced from PatrickJS/awesome-cursorrules — the community registry for .mdc Project Rules.

The rules

# Role & Perspective
You are a Senior iOS Engineer and SwiftUI Expert. You are also an expert in Clean Architecture, SOLID Principles, Design Patterns (MVVM-C, VIPER), and Performance Optimization.

# Code Generation Guidelines

## 1. General Principles
- Language: Swift 6.0+ (Strict Concurrency).
- Framework: SwiftUI (Targeting iOS 15+).
- Architecture: MVVM or Clean Architecture. Use Coordinators for complex navigation.
- Design Principles (SOLID): SRP, OCP, LSP, ISP, DIP.
- Safety: STRICTLY enforce Swift 6 concurrency safety (Sendable, MainActor, actor). Treat warnings as errors.

## 2. SwiftUI Best Practices (Performance First)
- View Composition:
  - Strict Size Limit: body MUST NOT exceed 50 lines. Relentlessly extract subviews.
  - GeometryReader: Avoid unless absolutely necessary. Prefer .background(GeometryReader ...) or Layout protocol.
- Modifiers:
  - Hit Testing: Add .contentShape(Rectangle()) to rows with transparent backgrounds.
  - Shorthand Syntax: Prefer type-inferred dot syntax (e.g., .background(.blue)).
- State Management:
  - @State: local value-type properties.
  - @StateObject: ONLY in the view that creates/owns the lifecycle.
  - @ObservedObject: child views that react but do not own.
  - @EnvironmentObject: sparingly. Prefer explicit DI via init.
- Lists & Grids:
  - LazyVStack / LazyHStack for dynamic content.
  - Always use stable id (avoid \.self).
- Animation:
  - Use .animation(_:value:) explicitly linked to a state variable.
  - Avoid withAnimation inside body for state changes unless triggered by user interaction.
  - TimelineView for high-frequency updates instead of Timer.

## 3. Swift 6 Concurrency & Threading
- Main Thread: UI updates MUST run on the MainActor.
  - Annotate ViewModels with @MainActor.
  - Use MainActor.run { ... } or Task { @MainActor in ... } for context switching.
- Lifecycle: Prefer .task(id: ...) over .onAppear (auto-cancels async work).
- Data Layer: actor for shared mutable state/services. nonisolated for pure logic.
- Blocking: NEVER block the main thread. Heavy work goes to a detached Task.

## 4. Memory Management & Safety
- Closures: Default to [weak self]. Strictly use guard let self else { return } at the start of async closures. ONLY use [unowned self] with proven lifecycle.
- Image Handling: AsyncImage (with caching) or Nuke/Kingfisher. Always apply .resizable() immediately.

## 5. Coding Style & Naming
- Naming: Verbose and clear. fetchUserData > getData.
- Structure: MARK: - Section Name. Private helpers in private extension.
- Previews: Always provide #Preview (Xcode 15+) or PreviewProvider. Inject Mock data.

## 6. Testing Strategy
- Unit Tests: Given-When-Then pattern.
- Mocks: Protocol-based Mocks for external dependencies.
- UITests: distinct accessibilityIdentifier strings.

# Response Format
- Block-based: Return code in formatted code blocks.
- Reasoning: Briefly explain why a specific approach was chosen.
- Diffs: Prioritize showing specific changes or full corrected context.

Installation

Drop the .mdc into .cursor/rules/ of your SwiftUI project:

mkdir -p .cursor/rules
curl -sL \
  https://raw.githubusercontent.com/PatrickJS/awesome-cursorrules/main/rules/swiftui-guidelines-cursorrules-prompt-file.mdc \
  -o .cursor/rules/swiftui-guidelines.mdc

Cursor picks up files in .cursor/rules/ automatically.

Example View + ViewModel

@MainActor
final class UserListViewModel: ObservableObject {
    @Published private(set) var users: [User] = []
    @Published private(set) var isLoading = false

    private let service: UserService

    init(service: UserService = .live) {
        self.service = service
    }

    func load() async {
        isLoading = true
        defer { isLoading = false }
        do {
            users = try await service.fetchUsers()
        } catch {
            // typed domain error, not generic Exception
        }
    }
}

struct UserListView: View {
    @StateObject private var viewModel = UserListViewModel()

    var body: some View {
        // keep this under ~50 lines — extract subviews aggressively
        NavigationStack {
            List(viewModel.users) { user in
                UserRow(user: user)
                    .contentShape(Rectangle())
            }
            .task { await viewModel.load() }
            .navigationTitle("Users")
        }
    }
}

Why it helps

Without SwiftUI-aware rules, models will:

  • Stuff a 200-line body into one struct (every redraw re-evaluates the whole tree)
  • Use GeometryReader instead of Layout protocol or .background(GeometryReader ...)
  • Pass Timer publishers where TimelineView is the right tool
  • Reach for \.self as a list id (crashes on duplicate IDs, and breaks animations)
  • Skip @MainActor annotations on ViewModels and get “publishing changes from background threads” warnings
  • Use .onAppear { Task { await load() } } and never cancel — leaks when the view pops mid-flight
  • Forget [weak self] in async closures, leaking the ViewModel

These rules turn those mistakes into compile errors before they ship.

Gotchas

  • The body 50-line limit is enforced by the rule, not the compiler. Pair with an Xcode file-template or SwiftLint custom rule if your team needs the teeth.
  • alwaysApply: false in the frontmatter — these are Project Rules you opt into per repo, not a global hook.
  • These are Cursor .mdc rules, not a Claude Code CLAUDE.md file. For Claude Code, paraphrase into your project’s CLAUDE.md.
  • “Strict concurrency” assumes Swift 6 mode. On Swift 5.x toolchains you’ll see a flood of warnings — fix the warnings or upgrade.
  • #Preview requires Xcode 15+. On older Xcode the rule wants PreviewProvider instead.

## embed this badge

cache ✓ in cache.directory
![cached](https://cache.directory/badge/swiftui-guidelines.svg)