swift-ios-app

bgrober's avatarfrom bgrober

Use when creating iOS apps, setting up Xcode projects, designing app architecture, implementing SwiftUI views, using SwiftData models, adding Swift 6 concurrency with actors, or integrating Supabase backend. Triggers on "new iOS app", "Swift architecture", "SwiftData setup", "actor pattern", "iOS project structure".

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 7, 2026⬇️1downloads

When & Why to Use This Skill

This Claude skill acts as a comprehensive architect for modern iOS development, providing a structured framework for building scalable and maintainable applications. It specializes in bridging SwiftUI, SwiftData, and Swift 6 concurrency patterns with Supabase backend integration, ensuring developers follow industry best practices for project organization and thread-safe state management.

Use Cases

  • Case 1: Bootstrapping a new iOS project with a professional-grade directory structure, including dedicated layers for Models, Services, ViewModels, and Views.
  • Case 2: Implementing Swift 6 strict concurrency by utilizing Actors for thread-safe service layers and @MainActor for UI-bound state mutations.
  • Case 3: Setting up robust local persistence with SwiftData, including advanced patterns like denormalized fields and custom result enums for state tracking.
  • Case 4: Integrating Supabase for backend services, covering Apple Sign-In authentication flows, bidirectional data synchronization, and automatic 401 token refresh logic.
  • Case 5: Establishing a consistent design system within Xcode using Theme-based extensions for colors, typography, and reusable UI components.
nameswift-ios-app
descriptionUse when creating iOS apps, setting up Xcode projects, designing app architecture, implementing SwiftUI views, using SwiftData models, adding Swift 6 concurrency with actors, or integrating Supabase backend. Triggers on "new iOS app", "Swift architecture", "SwiftData setup", "actor pattern", "iOS project structure".

Swift iOS App Architecture

Guide for building iOS apps with SwiftUI, SwiftData, Swift 6 concurrency, and Supabase backend.

When to Use

  • Starting a new iOS project
  • Understanding existing iOS app architecture
  • Adding a major new feature that needs architectural guidance
  • Reviewing project structure decisions

Project Structure

AppName/
├── AppNameApp.swift          # App entry, configures appearance and SwiftData
├── Models/
│   ├── Item.swift            # SwiftData models (local persistence)
│   ├── ItemResult.swift      # Enum: pending/success/failed states
│   └── Enums/                # Supporting enums (ItemType, etc.)
├── Services/
│   ├── AuthService.swift     # @Observable auth state, trial flow
│   ├── SupabaseClient.swift  # Actor for auth/token management
│   ├── SupabaseService.swift # Actor: storage upload and DB sync
│   ├── SyncService.swift     # Actor: bidirectional sync
│   ├── ItemService.swift     # Actor: business logic for items
│   ├── ImageManager.swift    # Local image caching
│   ├── NetworkMonitor.swift  # NWPathMonitor for connectivity
│   └── PermissionsManager.swift # Camera, photos, etc.
├── Utilities/
│   ├── Constants.swift       # Supabase URL, anon key
│   └── Theme.swift           # Design system, colors, typography
├── ViewModels/
│   └── FeatureViewModel.swift # Complex feature state machines
└── Views/
    ├── RootView.swift        # Routes based on auth state
    ├── MainTabView.swift     # Tab navigation
    ├── SplashView.swift      # Launch animation
    ├── Auth/                 # SignInView, TrialPrompt
    ├── Feature/              # Feature-specific views
    ├── Components/           # Reusable: badges, cards, empty states
    ├── Onboarding/           # OnboardingFlow
    └── Settings/             # SettingsView

Key Patterns

1. Swift 6 Concurrency

Services as Actors:

actor SupabaseService {
    private let client: SupabaseClient

    func uploadImage(_ data: Data, path: String) async throws -> URL {
        // Actor-isolated state is thread-safe
    }
}

UI-Bound State with @MainActor:

@MainActor @Observable
final class AuthService {
    var isAuthenticated = false
    var currentUser: User?

    func signIn() async throws {
        // All state mutations happen on MainActor
    }
}

Sendable Data Transfer:

struct ItemSyncData: Sendable {
    let id: UUID
    let title: String
    let createdAt: Date
}

Bridging SwiftData:

@preconcurrency import SwiftData

@Model
final class Item: @unchecked Sendable {
    // All access via MainActor
}

2. SwiftData Models

Model with Denormalized Fields:

@Model
final class Item {
    var id: UUID
    var createdAt: Date
    var title: String

    // Denormalized for efficient queries
    var resultType: String  // "pending", "success", "failed"
    var score: Int?

    // Full result stored as associated value
    var result: ItemResult = .pending

    init(title: String) {
        self.id = UUID()
        self.createdAt = Date()
        self.title = title
        self.resultType = "pending"
    }
}

Result Enum Pattern:

enum ItemResult: Codable, Equatable {
    case pending
    case success(SuccessData)
    case failed(String)
}

3. Authentication Flow

AuthService Structure:

@MainActor @Observable
final class AuthService {
    private let supabaseClient: SupabaseClient

    var isAuthenticated = false
    var isTrialUsed = false
    var currentUser: User?

    func checkAuthState() async {
        // Check Keychain for existing session
    }

    func signInWithApple() async throws {
        // Apple Sign-In → Supabase Auth
    }

    func signOut() async {
        // Clear Keychain, reset state
    }
}

Root View Routing:

struct RootView: View {
    @Environment(AuthService.self) var authService

    var body: some View {
        Group {
            if authService.isAuthenticated {
                MainTabView()
            } else {
                SignInView()
            }
        }
    }
}

4. Data Sync Pattern

Upload on Create:

// In ViewModel after creating local item
Task {
    await supabaseService.syncItem(item)
}

Download on Login:

// In AuthService after successful login
await syncService.syncRemoteItems()

Conflict Resolution:

// Use upsert to handle conflicts
func syncItem(_ item: Item) async throws {
    try await client
        .from("items")
        .upsert(item.toRemoteData(), onConflict: "id")
        .execute()
}

5. Token Refresh Pattern

Automatic 401 Retry:

func makeAuthenticatedRequest<T>(_ request: () async throws -> T) async throws -> T {
    do {
        return try await request()
    } catch let error as HTTPError where error.statusCode == 401 {
        try await refreshToken()
        return try await request()  // Retry once
    }
}

Design System Integration

Theme.swift Structure:

// Colors
extension Color {
    static let appAccent = Color("AccentColor")
    static let cardBackground = Color("CardBackground")
}

// Typography
extension View {
    func appHeadline() -> some View {
        self.font(.system(.headline, design: .rounded))
    }
}

// View Modifiers
extension View {
    func appCard() -> some View {
        self.padding()
            .background(Color.cardBackground)
            .cornerRadius(12)
    }
}

// Haptics
struct HapticManager {
    static func impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
        let generator = UIImpactFeedbackGenerator(style: style)
        generator.impactOccurred()
    }
}

File Naming Conventions

Type Convention Example
SwiftData Model Singular noun Pour.swift
Service Actor *Service.swift GradingService.swift
View *View.swift CaptureView.swift
ViewModel *ViewModel.swift CaptureViewModel.swift
Result Enum *Result.swift PourResult.swift
Manager *Manager.swift ImageManager.swift

Common Gotchas

SwiftData + Actors

  • Never pass ModelContext across actor boundaries
  • Use @preconcurrency import SwiftData
  • Mark models as @unchecked Sendable if accessed only via MainActor

Swift 6 Concurrency

  • Use nonisolated for pure functions that don't access actor state
  • Check SWIFT_APPROACHABLE_CONCURRENCY = YES build setting
  • Use Sendable structs for data transfer between actors

Supabase Auth

  • Store refresh tokens in Keychain, not UserDefaults
  • Handle token refresh on 401 automatically
  • Use service_role key only in Edge Functions, never in app

Checklist: New iOS App

[ ] Create Xcode project with SwiftData
[ ] Set up folder structure (Models, Services, Views, etc.)
[ ] Add Supabase Swift SDK
[ ] Create Constants.swift with Supabase URL/key
[ ] Create Theme.swift with design system
[ ] Set up AuthService with Apple Sign-In
[ ] Create SupabaseClient actor
[ ] Add NetworkMonitor for connectivity
[ ] Create RootView with auth routing
[ ] Set up MainTabView navigation
[ ] Enable Swift 6 strict concurrency mode