From ios-swift-skills
Guides iOS app architecture using Swift 6, iOS 18+, SwiftUI, SwiftData, Observation framework, and concurrency. Modernizes legacy patterns like Core Data, ObservableObject, and GCD.
npx claudepluginhub fal3/claude-skills-collection --plugin ios-swift-skillsThis skill uses the workspace's default tool permissions.
Build iOS apps using Swift 6 and iOS 18+ best practices. This skill ensures code uses modern patterns: SwiftData (not Core Data), Observation framework (not Combine), Swift concurrency (not GCD), and current SwiftUI APIs.
Provides expert Swift and SwiftUI guidance for iOS/macOS development using iOS 17+ patterns like @Observable, MVVM/TCA architecture, property wrappers, and production-ready code.
Builds iOS/macOS/watchOS/tvOS apps with SwiftUI views, state management, protocol-oriented architectures, async/await concurrency, actors for thread safety, and debugs Swift issues.
Builds iOS/macOS/watchOS/tvOS apps with Swift 5.9+, implements SwiftUI views and state management, async/await concurrency, actors, protocol-oriented architectures, and debugs Swift issues.
Share bugs, ideas, or general feedback.
Build iOS apps using Swift 6 and iOS 18+ best practices. This skill ensures code uses modern patterns: SwiftData (not Core Data), Observation framework (not Combine), Swift concurrency (not GCD), and current SwiftUI APIs.
Always use Swift concurrency (async/await, actor, @MainActor) instead of GCD or completion handlers. Use structured concurrency (TaskGroup, async let) over unstructured tasks.
Use @Observable macro for state management instead of ObservableObject with @Published. The Observation framework is more efficient and has cleaner syntax.
For new projects, always use SwiftData with @Model and @Query. SwiftData provides simpler APIs while maintaining Core Data's power.
Use NavigationStack (not NavigationView), @Entry for environment values, .task modifier for async work, and built-in components like ContentUnavailableView.
Use enums instead of strings for identifiers, typed throws for specific errors, and proper Sendable conformance for thread safety.
Prefer structs and enums over classes unless reference semantics are required. Use actor for thread-safe shared mutable state.
Activate this skill when:
@Observable
final class ViewModel {
// Private dependencies
private let service: ServiceProtocol
// Public readable state
private(set) var data: [Item] = []
private(set) var isLoading = false
private(set) var error: Error?
// User input state (use @Bindable in view)
var searchText = ""
var selectedFilter: Filter = .all
init(service: ServiceProtocol) {
self.service = service
}
// Public actions
func loadData() async {
isLoading = true
defer { isLoading = false }
do {
data = try await service.fetchData()
} catch {
self.error = error
}
}
}
struct ContentView: View {
@Bindable var viewModel: ViewModel
var body: some View {
content
.task { await viewModel.loadData() }
}
@ViewBuilder
private var content: some View {
if viewModel.isLoading {
ProgressView()
} else {
List(viewModel.data) { item in
ItemRow(item: item)
}
}
}
}
import SwiftData
@Model
final class Item {
var name: String
var createdAt: Date
@Relationship(deleteRule: .cascade) var children: [ChildItem]
init(name: String) {
self.name = name
self.createdAt = Date()
self.children = []
}
}
// In SwiftUI view
@Query(sort: \Item.createdAt, order: .reverse)
private var items: [Item]
// With filter
@Query(filter: #Predicate<Item> { $0.isComplete })
private var completedItems: [Item]
// With dynamic predicate
@Query private var items: [Item]
init(searchText: String) {
let predicate = #Predicate<Item> { item in
searchText.isEmpty || item.name.contains(searchText)
}
_items = Query(filter: predicate)
}
@Environment(\.modelContext) private var modelContext
func addItem() {
let item = Item(name: "New")
modelContext.insert(item)
try? modelContext.save()
}
func deleteItem(_ item: Item) {
modelContext.delete(item)
try? modelContext.save()
}
Create an actor for thread-safe API operations:
actor APIClient {
private let session: URLSession
private let decoder: JSONDecoder
init(session: URLSession = .shared) {
self.session = session
self.decoder = JSONDecoder()
}
func fetch<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
let (data, response) = try await session.data(for: endpoint.urlRequest)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw APIError.invalidResponse
}
return try decoder.decode(T.self, from: data)
}
}
Use type-safe navigation with NavigationStack:
struct AppView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
RootView()
.navigationDestination(for: Item.self) { item in
ItemDetailView(item: item)
}
.navigationDestination(for: User.self) { user in
UserProfileView(user: user)
}
}
}
}
Use the modern Swift Testing framework instead of XCTest:
import Testing
@Test("View model loads data successfully")
func dataLoading() async throws {
let viewModel = ViewModel(service: MockService())
await viewModel.loadData()
#expect(viewModel.data.isEmpty == false)
}
@Test("Validation fails with invalid input", arguments: [
"invalid-email",
"missing@",
"@domain.com"
])
func emailValidation(invalidEmail: String) throws {
#expect(throws: ValidationError.self) {
try validateEmail(invalidEmail)
}
}
Before writing code, verify you're using:
@Observable NOT ObservableObject@Query NOT @FetchRequestNavigationStack NOT NavigationViewasync/await NOT completion handlers@MainActor NOT DispatchQueue.main.asyncactor NOT serial DispatchQueueSwiftData.ModelContext NOT NSManagedObjectContext@Test NOT XCTestthrows(ErrorType) when appropriateLoad when you need detailed guidance:
modern-patterns.md - Comprehensive patterns for Swift 6/iOS 18+
anti-patterns.md - What NOT to do and why
examples.md - Complete working implementations
Need state management?
→ Use @Observable for view models
→ Use @State for simple view-local state
→ Use @Environment for dependency injection
Need data persistence?
→ Use SwiftData with @Model and @Query
→ Never use Core Data for new code
Need async operations?
→ Use async/await and structured concurrency
→ Mark UI-bound code with @MainActor
→ Use actor for thread-safe shared state
Need navigation?
→ Use NavigationStack with NavigationPath
→ Type-safe destinations with .navigationDestination(for:)
Need API calls?
→ Create an actor with async throws methods
→ Use URLSession.data(from:) with async/await
This skill actively prevents these outdated patterns:
NSManagedObject, @FetchRequest)ObservableObject, @Published, .sink)DispatchQueue, DispatchGroup)NavigationView, NavigationLink(destination:))Thread, NSOperationQueue)async/await is availableWhen encountering these patterns in existing code, suggest modern alternatives from the references.