iOS Development Skill
Quick orientation
Before anything else, identify:
- UI framework → SwiftUI / UIKit / mixed?
- Architecture → MVVM / TCA / VIPER / Clean / MVC?
- iOS target → iOS 16 / 17 / 18? (affects API availability)
- Task type → new feature / debug / refactor / architecture / performance / release?
- Graphics intensity → стандартный UI / средняя 3D / тяжёлая графика / Metal?
Если задача связана с Metal, GPU, рендерингом, шейдерами, TBDR, MetalFX, GPU profiling, frame pacing, PSO, heaps, ICB, MTLIO → читать references/metal-graphics.md первым.
Then read the relevant reference file before writing code.
Reference files — read when needed
| Topic |
File |
When to read |
| SwiftUI pipeline |
references/swiftui.md |
SwiftUI views, state, bindings, modifiers, animations |
| UIKit pipeline |
references/uikit.md |
UIViewController, Auto Layout, delegates, storyboards |
| Architecture |
references/architecture.md |
MVVM, TCA, VIPER, Clean, Coordinator |
| Networking |
references/networking.md |
URLSession, async/await, REST/GraphQL, auth |
| Data persistence |
references/data.md |
CoreData, SwiftData, UserDefaults, Keychain, FileManager |
| Navigation |
references/navigation.md |
NavigationStack, Coordinator, deep links, sheets |
| Performance |
references/performance.md |
Instruments, memory, launch time, rendering |
| Metal & Heavy Graphics |
references/metal-graphics.md |
Metal, GPU рендер, TBDR, PSO/heaps/argument buffers, ICB, MTLIO, MetalFX, терморегуляция, профилирование GPU |
iOS version matrix (API availability)
| Feature |
Min iOS |
| SwiftUI |
iOS 13 |
async/await |
iOS 15 |
| NavigationStack |
iOS 16 |
| SwiftData |
iOS 17 |
@Observable macro |
iOS 17 |
| RippleEffect, ScrollView phases |
iOS 18 |
Always check target before using newer APIs. Use #available guards for backward compatibility:
if #available(iOS 17, *) {
// SwiftData or @Observable
} else {
// fallback
}
Core Swift patterns (always apply)
Concurrency — async/await first
// Prefer this
func loadUser() async throws -> User {
let data = try await URLSession.shared.data(from: url).0
return try JSONDecoder().decode(User.self, from: data)
}
// Call site
Task {
do {
user = try await loadUser()
} catch {
errorMessage = error.localizedDescription
}
}
Error handling — typed errors
enum AppError: LocalizedError {
case networkUnavailable
case decodingFailed(String)
case unauthorized
var errorDescription: String? {
switch self {
case .networkUnavailable: return "No internet connection"
case .decodingFailed(let detail): return "Data error: \(detail)"
case .unauthorized: return "Please sign in again"
}
}
}
Value types first
Prefer struct over class unless you need reference semantics, inheritance, or @Observable.
Architecture decision tree
Is the screen purely data-display with simple interaction?
YES → MVC / simple SwiftUI View with @State is fine
NO ↓
Is business logic testable without UI?
Required → use ViewModel (MVVM) or Store (TCA)
Is navigation/routing complex (deep links, coordinator)?
YES → read references/navigation.md → Coordinator pattern
Is the team large or is this a long-lived product?
YES → Clean Architecture or TCA → read references/architecture.md
Default for most apps: MVVM + Coordinator
State management quick reference
SwiftUI state hierarchy
Local transient state → @State
Passed from parent → @Binding
Shared across views → @StateObject / @ObservedObject (iOS 16-)
→ @State with @Observable class (iOS 17+)
App-wide environment → @EnvironmentObject / @Environment
iOS 17+ preferred (@Observable)
@Observable
class UserStore {
var users: [User] = []
var isLoading = false
}
// In View — no property wrapper needed
struct UserListView: View {
var store: UserStore // just pass it
var body: some View {
List(store.users) { Text($0.name) }
}
}
iOS 16 and below (ObservableObject)
class UserViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
}
struct UserListView: View {
@StateObject private var vm = UserViewModel()
}
Xcode & project setup checklist
New project:
Capabilities to enable when needed:
- Push Notifications → add
Push Notifications capability
- Background fetch →
Background Modes
- iCloud/CloudKit →
iCloud
- Sign in with Apple →
Sign In with Apple
Testing baseline
// Unit test — ViewModel
@MainActor
final class UserViewModelTests: XCTestCase {
func testLoadUsers() async throws {
let vm = UserViewModel(service: MockUserService())
await vm.loadUsers()
XCTAssertFalse(vm.users.isEmpty)
}
}
// UI test — basic
func testLoginFlow() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
app.textFields["Email"].typeText("test@example.com")
app.buttons["Continue"].tap()
XCTAssertTrue(app.navigationBars["Home"].exists)
}
Common pitfalls
| Pitfall |
Fix |
| Purple warning: "Publishing changes from background thread" |
Wrap in await MainActor.run { } or mark func @MainActor |
| View re-renders too often |
Audit @State/@Published — split large ViewModels |
| Memory leak in closures |
Use [weak self] captures in escaping closures |
@StateObject recreated |
Move object creation up the tree; don't init in body |
| Keyboard covering text field |
Use .ignoresSafeArea(.keyboard, edges: .bottom) or ScrollView |
| Slow list |
Use LazyVStack or List with id: stability |
| Simulator ≠ device behavior |
Always test on real device before submission |
| Metal: CPU↔GPU stall |
Triple buffer + DispatchSemaphore; никогда не ждать GPU синхронно в draw() |
| Metal: PSO компилируется в рантайме |
Создавать все PSO до первого кадра, кешировать |
| Metal: лишние load/store |
storeAction = .dontCare для transient (depth buffer) |
| Metal: OOM jetsam |
Мониторить currentAllocatedSize vs recommendedMaxWorkingSetSize; освобождать при warning |
| Metal: статтер при загрузке |
MTLIO + MTLSharedEvent вместо блокирующей загрузки; ODR для тяжёлых ассетов |
| Metal: перегрев |
Подписаться на thermalStateDidChangeNotification, адаптировать render scale + FPS |
Hardware budget ориентиры (GPU capability levels)
Планировать по capability-уровням, не под конкретную модель. Всегда включать adaptive quality.
| Уровень |
SoC |
Реалистичный target |
Ключевые ограничения |
| Широкий охват |
A13–A15 (iPhone SE, 13, 14) |
30–60 FPS, агрессивный render scale |
Строгий бюджет RT; MetalFX обязателен для сложных сцен |
| Средний |
A16 (iPhone 15), M1 |
60 FPS при хорошем пайплайне |
MetalFX + динамический render scale |
| Флагман |
A17 Pro (iPhone 15 Pro), M3+ |
60–120 FPS, тяжёлые эффекты |
Ray tracing (аппаратный); всё равно нужен thermal management |
Важно: даже флагманы упираются в термальную стабильность при длительной нагрузке, а не в пиковую мощность GPU.
App Store submission checklist
1---2name: ios-development3description: Comprehensive iOS app development skill. Use this skill for ANY iOS-related task: writing Swift/SwiftUI/UIKit code, architecting apps, debugging crashes, setting up navigation, networking, data persistence, animations, performance optimization, App Store submission, Xcode configuration. Trigger when user mentions: iOS, Swift, SwiftUI, UIKit, Xcode, iPhone/iPad app, Combine, CoreData, SwiftData, MVVM, TCA, URLSession, async/await, @State/@Binding/@ObservableObject, NavigationStack, XCTest, TestFlight, provisioning profiles, or any Apple platform development. Always use this skill before writing iOS code or architecture. Do NOT use for web frontends (HTML/CSS/JS, React/Vue, browser UI) — even a WebView's page content is web work; use frontend-design for that. This skill is native Apple-platform code only.4---56# iOS Development Skill78## Quick orientation910Before anything else, identify:111. **UI framework** → SwiftUI / UIKit / mixed?122. **Architecture** → MVVM / TCA / VIPER / Clean / MVC?133. **iOS target** → iOS 16 / 17 / 18? (affects API availability)144. **Task type** → new feature / debug / refactor / architecture / performance / release?155. **Graphics intensity** → стандартный UI / средняя 3D / тяжёлая графика / Metal?1617Если задача связана с **Metal, GPU, рендерингом, шейдерами, TBDR, MetalFX, GPU profiling, frame pacing, PSO, heaps, ICB, MTLIO** → читать `references/metal-graphics.md` первым.1819Then read the relevant reference file before writing code.2021---2223## Reference files — read when needed2425| Topic | File | When to read |26|---|---|---|27| SwiftUI pipeline | `references/swiftui.md` | SwiftUI views, state, bindings, modifiers, animations |28| UIKit pipeline | `references/uikit.md` | UIViewController, Auto Layout, delegates, storyboards |29| Architecture | `references/architecture.md` | MVVM, TCA, VIPER, Clean, Coordinator |30| Networking | `references/networking.md` | URLSession, async/await, REST/GraphQL, auth |31| Data persistence | `references/data.md` | CoreData, SwiftData, UserDefaults, Keychain, FileManager |32| Navigation | `references/navigation.md` | NavigationStack, Coordinator, deep links, sheets |33| Performance | `references/performance.md` | Instruments, memory, launch time, rendering |34| **Metal & Heavy Graphics** | `references/metal-graphics.md` | Metal, GPU рендер, TBDR, PSO/heaps/argument buffers, ICB, MTLIO, MetalFX, терморегуляция, профилирование GPU |3536---3738## iOS version matrix (API availability)3940| Feature | Min iOS |41|---|---|42| SwiftUI | iOS 13 |43| `async/await` | iOS 15 |44| NavigationStack | iOS 16 |45| SwiftData | iOS 17 |46| `@Observable` macro | iOS 17 |47| RippleEffect, ScrollView phases | iOS 18 |4849Always check target before using newer APIs. Use `#available` guards for backward compatibility:50```swift51if #available(iOS 17, *) {52 // SwiftData or @Observable53} else {54 // fallback55}56```5758---5960## Core Swift patterns (always apply)6162### Concurrency — async/await first63```swift64// Prefer this65func loadUser() async throws -> User {66 let data = try await URLSession.shared.data(from: url).067 return try JSONDecoder().decode(User.self, from: data)68}6970// Call site71Task {72 do {73 user = try await loadUser()74 } catch {75 errorMessage = error.localizedDescription76 }77}78```7980### Error handling — typed errors81```swift82enum AppError: LocalizedError {83 case networkUnavailable84 case decodingFailed(String)85 case unauthorized86 87 var errorDescription: String? {88 switch self {89 case .networkUnavailable: return "No internet connection"90 case .decodingFailed(let detail): return "Data error: \(detail)"91 case .unauthorized: return "Please sign in again"92 }93 }94}95```9697### Value types first98Prefer `struct` over `class` unless you need reference semantics, inheritance, or `@Observable`.99100---101102## Architecture decision tree103104```105Is the screen purely data-display with simple interaction?106 YES → MVC / simple SwiftUI View with @State is fine107 NO ↓108109Is business logic testable without UI?110 Required → use ViewModel (MVVM) or Store (TCA)111112Is navigation/routing complex (deep links, coordinator)?113 YES → read references/navigation.md → Coordinator pattern114115Is the team large or is this a long-lived product?116 YES → Clean Architecture or TCA → read references/architecture.md117118Default for most apps: MVVM + Coordinator119```120121---122123## State management quick reference124125### SwiftUI state hierarchy126```127Local transient state → @State128Passed from parent → @Binding129Shared across views → @StateObject / @ObservedObject (iOS 16-)130 → @State with @Observable class (iOS 17+)131App-wide environment → @EnvironmentObject / @Environment132```133134### iOS 17+ preferred (@Observable)135```swift136@Observable137class UserStore {138 var users: [User] = []139 var isLoading = false140}141142// In View — no property wrapper needed143struct UserListView: View {144 var store: UserStore // just pass it145 var body: some View {146 List(store.users) { Text($0.name) }147 }148}149```150151### iOS 16 and below (ObservableObject)152```swift153class UserViewModel: ObservableObject {154 @Published var users: [User] = []155 @Published var isLoading = false156}157158struct UserListView: View {159 @StateObject private var vm = UserViewModel()160}161```162163---164165## Xcode & project setup checklist166167**New project:**168- [ ] Set deployment target explicitly169- [ ] Enable Swift strict concurrency warnings (`SWIFT_STRICT_CONCURRENCY = complete`)170- [ ] Add `.gitignore` for Xcode (xcuserdata, DerivedData)171- [ ] Configure signing (automatic vs manual)172- [ ] Set bundle ID and version/build numbers173174**Capabilities to enable when needed:**175- Push Notifications → add `Push Notifications` capability176- Background fetch → `Background Modes`177- iCloud/CloudKit → `iCloud`178- Sign in with Apple → `Sign In with Apple`179180---181182## Testing baseline183184```swift185// Unit test — ViewModel186@MainActor187final class UserViewModelTests: XCTestCase {188 func testLoadUsers() async throws {189 let vm = UserViewModel(service: MockUserService())190 await vm.loadUsers()191 XCTAssertFalse(vm.users.isEmpty)192 }193}194195// UI test — basic196func testLoginFlow() {197 let app = XCUIApplication()198 app.launch()199 app.textFields["Email"].tap()200 app.textFields["Email"].typeText("test@example.com")201 app.buttons["Continue"].tap()202 XCTAssertTrue(app.navigationBars["Home"].exists)203}204```205206---207208## Common pitfalls209210| Pitfall | Fix |211|---|---|212| Purple warning: "Publishing changes from background thread" | Wrap in `await MainActor.run { }` or mark func `@MainActor` |213| View re-renders too often | Audit `@State`/`@Published` — split large ViewModels |214| Memory leak in closures | Use `[weak self]` captures in escaping closures |215| `@StateObject` recreated | Move object creation up the tree; don't init in body |216| Keyboard covering text field | Use `.ignoresSafeArea(.keyboard, edges: .bottom)` or ScrollView |217| Slow list | Use `LazyVStack` or `List` with `id:` stability |218| Simulator ≠ device behavior | Always test on real device before submission |219| **Metal: CPU↔GPU stall** | Triple buffer + DispatchSemaphore; никогда не ждать GPU синхронно в draw() |220| **Metal: PSO компилируется в рантайме** | Создавать все PSO до первого кадра, кешировать |221| **Metal: лишние load/store** | storeAction = .dontCare для transient (depth buffer) |222| **Metal: OOM jetsam** | Мониторить currentAllocatedSize vs recommendedMaxWorkingSetSize; освобождать при warning |223| **Metal: статтер при загрузке** | MTLIO + MTLSharedEvent вместо блокирующей загрузки; ODR для тяжёлых ассетов |224| **Metal: перегрев** | Подписаться на thermalStateDidChangeNotification, адаптировать render scale + FPS |225226---227228## Hardware budget ориентиры (GPU capability levels)229230Планировать по capability-уровням, не под конкретную модель. Всегда включать adaptive quality.231232| Уровень | SoC | Реалистичный target | Ключевые ограничения |233|---|---|---|---|234| **Широкий охват** | A13–A15 (iPhone SE, 13, 14) | 30–60 FPS, агрессивный render scale | Строгий бюджет RT; MetalFX обязателен для сложных сцен |235| **Средний** | A16 (iPhone 15), M1 | 60 FPS при хорошем пайплайне | MetalFX + динамический render scale |236| **Флагман** | A17 Pro (iPhone 15 Pro), M3+ | 60–120 FPS, тяжёлые эффекты | Ray tracing (аппаратный); всё равно нужен thermal management |237238**Важно:** даже флагманы упираются в термальную стабильность при длительной нагрузке, а не в пиковую мощность GPU.239240---241242## App Store submission checklist243244- [ ] Increment build number for every TestFlight upload245- [ ] Privacy manifest (`PrivacyInfo.xcprivacy`) required for sensitive APIs246- [ ] App icons all sizes provided (use Asset Catalog)247- [ ] Screenshots for all required device sizes248- [ ] Export compliance (encryption questions)249- [ ] App Review Information filled in250- [ ] No `UIRequiresFullScreen = NO` without iPad support reasoning