Swift and iOS development with modern patterns and best practices
/plugin marketplace add onmyway133/claude-code-plugins/plugin install onmyway133-super-plugins-super@onmyway133/claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
You are a Swift and iOS development expert. Apply these guidelines when working on Swift code.
@Observable for shared dataOrganize related functionality using extensions:
// MARK: - Main View
struct BooksView: View {
@State private var books: [Book] = []
var body: some View {
List(books) { book in
BookCell(book: book)
}
}
}
// MARK: - Subviews
extension BooksView {
struct BookCell: View {
let book: Book
var body: some View {
HStack {
BookCover(url: book.coverURL)
BookInfo(book: book)
}
}
}
struct BookCover: View {
let url: URL?
var body: some View { /* ... */ }
}
struct BookInfo: View {
let book: Book
var body: some View { /* ... */ }
}
}
// MARK: - View Model
extension BooksView {
@Observable
@MainActor
final class ViewModel {
var books: [Book] = []
var isLoading = false
func loadBooks() async { /* ... */ }
}
}
View structsFeatures/
├── Books/
│ ├── BooksView.swift
│ ├── BookDetailView.swift
│ └── Models/
├── Settings/
│ └── SettingsView.swift
└── Shared/
├── Components/
└── Extensions/
// Types: UpperCamelCase
struct UserProfile { }
enum NetworkError { }
protocol DataFetching { }
// Properties and methods: lowerCamelCase
var currentUser: User
func fetchUserData() async throws -> User
// Boolean properties: use assertion form
var isEnabled: Bool
var hasUnreadMessages: Bool
var canSubmit: Bool
// Collections: use plural nouns
var users: [User]
var selectedItems: Set<Item>
// Mutating vs non-mutating
func add(_ item: Item) // mutates
func adding(_ item: Item) -> [Item] // returns new
// Local UI state only
@State private var searchText = ""
// Observable objects (iOS 17+) - prefer @Observable over ObservableObject
@Observable
@MainActor
final class ProfileViewModel {
var user: User?
var isLoading = false
}
// In views
@State private var viewModel = ProfileViewModel()
// For passed-in observable objects
@Bindable var viewModel: ProfileViewModel
.foregroundStyle(.primary) // NOT foregroundColor()
.clipShape(.rect(cornerRadius: 12)) // NOT cornerRadius()
.bold() // NOT fontWeight(.bold)
.scrollIndicators(.hidden) // to hide scroll bars
// Modern Tab API - NOT tabItem()
TabView {
Tab("Home", systemImage: "house") {
HomeView()
}
Tab("Settings", systemImage: "gear") {
SettingsView()
}
}
// DON'T use single-parameter onChange
.onChange(of: value) { newValue in } // DEPRECATED
// DO use two-parameter version
.onChange(of: value) { oldValue, newValue in }
// For iOS 17+, prefer @Observable over ObservableObject
class MyViewModel: ObservableObject { } // Legacy (iOS 13-16)
@Observable class MyViewModel { } // Modern (iOS 17+)
// DON'T use UIScreen.main.bounds
let width = UIScreen.main.bounds.width // BAD
// DO use containerRelativeFrame or visualEffect
.containerRelativeFrame(.horizontal) { width, _ in
width * 0.8
}
// Prefer containerRelativeFrame() or visualEffect() over GeometryReader
// Use ImageRenderer instead of UIGraphicsImageRenderer
let renderer = ImageRenderer(content: myView)
if let image = renderer.uiImage { }
// DON'T use onTapGesture for buttons
Image(systemName: "plus")
.onTapGesture { action() } // BAD
// DO use Button with labels
Button {
action()
} label: {
Label("Add Item", systemImage: "plus")
}
// Only use onTapGesture when tracking tap location or count
// Modern navigation with type-safe destinations
NavigationStack {
List(items) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
.navigationDestination(for: Item.self) { item in
ItemDetailView(item: item)
}
}
// DO use enumerated directly
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
Text("\(index): \(item.name)")
}
// DON'T convert to array first unnecessarily
// Modern sleep - NOT Task.sleep(nanoseconds:)
try await Task.sleep(for: .seconds(1))
// NEVER use DispatchQueue.main.async()
DispatchQueue.main.async { } // BAD
// DO use MainActor
await MainActor.run { }
// or mark with @MainActor
// All @Observable classes should be @MainActor
@Observable
@MainActor
final class ViewModel { }
// Directory access
let documents = URL.documentsDirectory
let cache = URL.cachesDirectory
// Path manipulation
let file = documents.appending(path: "data.json")
// String operations - NOT replacingOccurrences(of:with:)
let result = text.replacing("old", with: "new")
// Text filtering - NOT contains()
items.filter { $0.name.localizedStandardContains(searchText) }
// Use format parameter - NOT C-style or NumberFormatter
Text(price, format: .currency(code: "USD"))
Text(date, format: .dateTime.month().day())
Text(count, format: .number.precision(.fractionLength(2)))
// Prefer static members over initializers
.buttonStyle(.borderedProminent) // NOT BorderedProminentButtonStyle()
.clipShape(.capsule) // NOT Capsule()
.clipShape(.circle) // NOT Circle()
.background(.ultraThinMaterial)
// DON'T use UIKit colors in SwiftUI
Color(UIColor.systemBackground) // BAD
// DO use SwiftUI colors
Color(.systemBackground) // OK
Color.primary
Color.secondary
// DON'T override Dynamic Type
.font(.system(size: 16)) // BAD - fixed size
// DO respect system font sizing
.font(.body)
.font(.headline)
// Avoid hard-coded padding and spacing values
@Model
final class Book {
var title: String
var author: String
var publishedDate: Date
var rating: Int?
@Relationship(deleteRule: .cascade)
var chapters: [Chapter]?
init(title: String, author: String, publishedDate: Date) {
self.title = title
self.author = author
self.publishedDate = publishedDate
}
}
When using iCloud sync:
@Attribute(.unique)// Define specific error types
enum DataError: LocalizedError {
case networkUnavailable
case invalidResponse(statusCode: Int)
case decodingFailed(underlying: Error)
var errorDescription: String? {
switch self {
case .networkUnavailable:
return "Network connection unavailable"
case .invalidResponse(let code):
return "Server returned error \(code)"
case .decodingFailed:
return "Failed to process server response"
}
}
}
// Avoid force try and force unwrap (except truly unrecoverable cases)
do {
let data = try await fetchData()
let result = try decoder.decode(Response.self, from: data)
} catch {
logger.error("Failed to load: \(error)")
throw DataError.decodingFailed(underlying: error)
}
import Testing
struct AuthenticationTests {
@Test("User can log in with valid credentials")
func successfulLogin() async throws {
let auth = AuthService()
let result = try await auth.login(email: "test@example.com", password: "valid")
#expect(result.isAuthenticated)
}
@Test("Login fails with wrong password")
func failedLogin() async {
let auth = AuthService()
await #expect(throws: AuthError.invalidCredentials) {
try await auth.login(email: "test@example.com", password: "wrong")
}
}
}
@Observable macro replaces ObservableObject@Bindable for bindings to observable objects#Index and #Unique@Previewable macro for simpler previewsif let, guard let, or nil coalescing@ViewBuilder or concrete typescontainerRelativeFrame first@Observable instead (iOS 17+)@MainActorThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.