| name | Swift |
| description | Swift and iOS development with modern patterns and best practices |
Swift
You are a Swift and iOS development expert. Apply these guidelines when working on Swift code.
Target Requirements
- Swift 6+ with modern Swift concurrency
- SwiftUI with
@Observable for shared data
- No third-party frameworks without explicit approval
- Avoid UIKit unless explicitly requested
- Follow Apple Human Interface Guidelines
Code Organization
Extension-Based Grouping
Organize related functionality using extensions:
struct BooksView: View {
@State private var books: [Book] = []
var body: some View {
List(books) { book in
BookCell(book: book)
}
}
}
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 { }
}
}
extension BooksView {
@Observable
@MainActor
final class ViewModel {
var books: [Book] = []
var isLoading = false
func loadBooks() async { }
}
}
File Structure
- Place each struct/class/enum in separate Swift files
- Don't break views into computed properties; use separate
View structs
- Organize by feature, not layer
Features/
├── Books/
│ ├── BooksView.swift
│ ├── BookDetailView.swift
│ └── Models/
├── Settings/
│ └── SettingsView.swift
└── Shared/
├── Components/
└── Extensions/
Naming Conventions
struct UserProfile { }
enum NetworkError { }
protocol DataFetching { }
var currentUser: User
func fetchUserData() async throws -> User
var isEnabled: Bool
var hasUnreadMessages: Bool
var canSubmit: Bool
var users: [User]
var selectedItems: Set<Item>
func add(_ item: Item)
func adding(_ item: Item) -> [Item]
SwiftUI Patterns
State Management
@State private var searchText = ""
@Observable
@MainActor
final class ProfileViewModel {
var user: User?
var isLoading = false
}
@State private var viewModel = ProfileViewModel()
@Bindable var viewModel: ProfileViewModel
Modern Modifiers (Use These)
.foregroundStyle(.primary)
.clipShape(.rect(cornerRadius: 12))
.bold()
.scrollIndicators(.hidden)
TabView {
Tab("Home", systemImage: "house") {
HomeView()
}
Tab("Settings", systemImage: "gear") {
SettingsView()
}
}
Deprecated Patterns to Avoid
.onChange(of: value) { newValue in }
.onChange(of: value) { oldValue, newValue in }
class MyViewModel: ObservableObject { }
@Observable class MyViewModel { }
Layout
let width = UIScreen.main.bounds.width
.containerRelativeFrame(.horizontal) { width, _ in
width * 0.8
}
let renderer = ImageRenderer(content: myView)
if let image = renderer.uiImage { }
Buttons and Gestures
Image(systemName: "plus")
.onTapGesture { action() }
Button {
action()
} label: {
Label("Add Item", systemImage: "plus")
}
Navigation
NavigationStack {
List(items) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
.navigationDestination(for: Item.self) { item in
ItemDetailView(item: item)
}
}
Collections
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
Text("\(index): \(item.name)")
}
Modern Swift APIs
Concurrency
try await Task.sleep(for: .seconds(1))
DispatchQueue.main.async { }
await MainActor.run { }
@Observable
@MainActor
final class ViewModel { }
Modern Foundation
let documents = URL.documentsDirectory
let cache = URL.cachesDirectory
let file = documents.appending(path: "data.json")
let result = text.replacing("old", with: "new")
items.filter { $0.name.localizedStandardContains(searchText) }
Text Formatting
Text(price, format: .currency(code: "USD"))
Text(date, format: .dateTime.month().day())
Text(count, format: .number.precision(.fractionLength(2)))
Static Member Lookup
.buttonStyle(.borderedProminent)
.clipShape(.capsule)
.clipShape(.circle)
.background(.ultraThinMaterial)
SwiftUI Colors
Color(UIColor.systemBackground)
Color(.systemBackground)
Color.primary
Color.secondary
Dynamic Type
.font(.system(size: 16))
.font(.body)
.font(.headline)
SwiftData
Basic Setup
@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
}
}
CloudKit Compatibility
When using iCloud sync:
- NEVER use
@Attribute(.unique)
- All properties need default values or be optional
- Mark ALL relationships as optional
Error Handling
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"
}
}
}
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)
}
Testing
Swift Testing Framework
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")
}
}
}
Testing Guidelines
- Write unit tests for core application logic
- UI tests only when unit tests aren't feasible
- Place view logic in view models for testability
iOS Version Features
iOS 17+
@Observable macro replaces ObservableObject
@Bindable for bindings to observable objects
- SwiftData for persistence
- TipKit for onboarding hints
iOS 18+
- Enhanced SwiftData with
#Index and #Unique
- Control Center widgets
@Previewable macro for simpler previews
iOS 26+
- Liquid Glass design system
- New translucent materials
- Modern Tab API
Things to Avoid
- Force unwrapping - Use
if let, guard let, or nil coalescing
- AnyView - Use
@ViewBuilder or concrete types
- GeometryReader - Try
containerRelativeFrame first
- UIScreen.main.bounds - Use proper layout APIs
- ObservableObject - Use
@Observable instead (iOS 17+)
- DispatchQueue.main.async - Use
@MainActor
- Hard-coded sizes - Respect Dynamic Type
- UIKit colors - Use SwiftUI colors
- Computed property views - Use separate View structs
- Single-param onChange - Use two-parameter version
Security
- NEVER commit secrets, API keys, or configuration data
- Use environment variables or secure storage
- Follow App Store Review Guidelines
Before Committing
- Run SwiftLint; fix all warnings and errors
- Ensure no force unwraps without justification
- Verify async code uses proper actors
- Check that views support Dynamic Type
- Write tests for business logic
- Add documentation comments as needed