1---2name: uikit-expert3description: Write, review, or improve UIKit code following best practices for view controller lifecycle, Auto Layout, collection views, navigation, animation, memory management, and modern iOS 18–26 APIs. Use when building new UIKit features, refactoring existing views or view controllers, reviewing code quality, adopting modern UIKit patterns (diffable data sources, compositional layout, cell configuration), or bridging UIKit with SwiftUI. Does not cover SwiftUI-only code.4---56# UIKit Expert Skill78## Overview9Use this skill to build, review, or improve UIKit features with correct lifecycle management, performant Auto Layout, modern collection view APIs, and safe navigation patterns. Prioritize native APIs, Apple's documented best practices, and performance-conscious patterns. This skill focuses on facts and best practices without enforcing specific architectural patterns (no MVVM/VIPER/Coordinator mandates).1011## Workflow Decision Tree1213### 1) Review existing UIKit code14- Check view controller lifecycle usage — `viewIsAppearing` for geometry, `viewDidLoad` for setup only (see `references/view-controller-lifecycle.md`)15- Verify Auto Layout correctness — batch activation, no constraint churn, `translatesAutoresizingMaskIntoConstraints` (see `references/auto-layout.md`)16- Check collection/table view APIs — diffable data sources, stable identity, CellRegistration (see `references/modern-collection-views.md`)17- Verify cell configuration uses `UIContentConfiguration`, not deprecated `textLabel` (see `references/cell-configuration.md`)18- Check list scroll performance — prefetching, cell reuse cleanup, reconfigureItems (see `references/list-performance.md`)19- Verify navigation patterns — bar appearance all 4 slots, no concurrent transition crashes (see `references/navigation-patterns.md`)20- Check animation correctness — API selection, PropertyAnimator state machine, constraint animation (see `references/animation-patterns.md`)21- Audit memory management — `[weak self]`, delegate ownership, Timer/CADisplayLink traps (see `references/memory-management.md`)22- Check concurrency safety — Task lifecycle, cancellation in viewDidDisappear (see `references/concurrency-main-thread.md`)23- If SwiftUI interop present — verify UIHostingController containment, sizingOptions (see `references/uikit-swiftui-interop.md`)24- Check image loading — downsampling, cell reuse race condition (cancel/clear/verify) (see `references/image-loading.md`)25- Verify keyboard handling — UIKeyboardLayoutGuide over manual notifications (see `references/keyboard-scroll.md`)26- Check trait handling and accessibility — registerForTraitChanges, Dynamic Type, VoiceOver (see `references/adaptive-appearance.md`)27- Validate modern API adoption and iOS 26+ availability handling (see `references/modern-uikit-apis.md`)2829### 2) Improve existing UIKit code30- Replace geometry work in `viewDidLoad` with `viewIsAppearing` (see `references/view-controller-lifecycle.md`)31- Eliminate constraint churn — create once, toggle `isActive` or modify `.constant` (see `references/auto-layout.md`)32- Migrate from legacy `UITableViewDataSource` to diffable data sources (see `references/modern-collection-views.md`)33- Replace deprecated `textLabel`/`detailTextLabel`/`imageView` with `UIContentConfiguration` (see `references/cell-configuration.md`)34- Replace `reloadItems` with `reconfigureItems` for in-place cell updates (see `references/list-performance.md`)35- Fix navigation bar appearance — set all 4 appearance slots, use `navigationItem` not `navigationBar` (see `references/navigation-patterns.md`)36- Improve animations — use PropertyAnimator for gestures, correct constraint animation pattern (see `references/animation-patterns.md`)37- Fix retain cycles — add `[weak self]`, cancel Tasks in `viewDidDisappear`, use block-based Timer (see `references/memory-management.md`)38- Migrate GCD to Swift concurrency — replace `DispatchQueue.main.async` with `Task` (see `references/concurrency-main-thread.md`)39- Suggest image downsampling when `UIImage(data:)` or full-resolution loading detected (as optional optimization, see `references/image-loading.md`)40- Replace keyboard notification handling with `UIKeyboardLayoutGuide` (see `references/keyboard-scroll.md`)41- Replace `traitCollectionDidChange` with `registerForTraitChanges` (see `references/adaptive-appearance.md`)42- Adopt iOS 26 APIs where appropriate — Observation, updateProperties(), .flushUpdates (see `references/modern-uikit-apis.md`)4344### 3) Implement new UIKit feature45- Design data flow first: identify owned state, injected dependencies, and model layer46- Set up view controller lifecycle correctly — one-time setup in `viewDidLoad`, geometry in `viewIsAppearing` (see `references/view-controller-lifecycle.md`)47- Build Auto Layout with batch activation and zero churn (see `references/auto-layout.md`)48- Use modern collection view stack: DiffableDataSource + CompositionalLayout + CellRegistration (see `references/modern-collection-views.md`)49- Configure cells with `UIContentConfiguration` and `configurationUpdateHandler` (see `references/cell-configuration.md`)50- Implement prefetching and proper cell reuse cleanup for lists (see `references/list-performance.md`)51- Set up navigation with all 4 appearance slots and concurrent-transition guards (see `references/navigation-patterns.md`)52- Choose correct animation API for the use case (see `references/animation-patterns.md`)53- Use `[weak self]` in escaping closures, cancel Tasks in lifecycle methods (see `references/memory-management.md`)54- Use `@MainActor` correctly, store Task references (see `references/concurrency-main-thread.md`)55- If embedding SwiftUI — use full child VC containment for UIHostingController (see `references/uikit-swiftui-interop.md`)56- Downsample images for display, handle cell reuse race condition (see `references/image-loading.md`)57- Use `UIKeyboardLayoutGuide` for keyboard handling (see `references/keyboard-scroll.md`)58- Support Dynamic Type, VoiceOver, dark mode from the start (see `references/adaptive-appearance.md`)59- Gate iOS 26+ features with `#available` and provide sensible fallbacks (see `references/modern-uikit-apis.md`)6061## Core Guidelines6263### View Controller Lifecycle64- Use `viewDidLoad` for one-time setup: subviews, constraints, delegates — NOT geometry65- Use `viewIsAppearing` (back-deployed iOS 13+) for geometry-dependent work, trait-based layout, scroll-to-item66- `viewDidLayoutSubviews` fires multiple times — use only for lightweight layer frame adjustments67- `viewWillAppear` is limited to transition coordinator animations and balanced notification registration68- Always call `super` in every lifecycle override69- Child VC containment: `addChild` → `addSubview` → `didMove(toParent:)` — in that exact order70- Verify deallocation with `deinit` logging during development7172### Auto Layout73- Always set `translatesAutoresizingMaskIntoConstraints = false` on programmatic views74- Use `NSLayoutConstraint.activate([])` — never individual `.isActive = true`75- Create constraints once, toggle `isActive` or modify `.constant` — never remove and recreate76- Never change priority from/to `.required` (1000) at runtime — use 99977- Animate constraints: update constant → call `layoutIfNeeded()` inside animation block on superview78- iOS 26+: use `.flushUpdates` option to simplify constraint animation79- Avoid deeply nested UIStackViews in reusable cells8081### Collection Views & Data Sources82- Use `UICollectionViewDiffableDataSource` with stable identifiers (UUID/database ID, not full model structs)83- Use `reconfigureItems` for content updates, `reloadItems` only when cell type changes84- Use `applySnapshotUsingReloadData` for initial population (bypasses diffing)85- Use `UICollectionViewCompositionalLayout` for any non-trivial layout86- Use `UICollectionView.CellRegistration` — no string identifiers, no manual casting87- Use `UIContentConfiguration` for cell content and `UIBackgroundConfiguration` for cell backgrounds88- Use `configurationUpdateHandler` for state-driven styling (selection, highlight)8990### Navigation91- Configure all 4 `UINavigationBarAppearance` slots (standard, scrollEdge, compact, compactScrollEdge)92- Set appearance on `navigationItem` (per-VC) in `viewDidLoad`, not on `navigationBar` in `viewWillAppear`93- Use `setViewControllers(_:animated:)` for deep links — not sequential push calls94- Guard against concurrent transitions — check `transitionCoordinator` before push/pop95- Set `prefersLargeTitles` once on the bar; use `largeTitleDisplayMode` per VC9697### Animation98- `UIView.animate` — simple one-shot animations; check `finished` in completion99- `UIViewPropertyAnimator` — gesture-driven, interruptible; respect state machine (inactive → active → stopped)100- `CABasicAnimation` — layer-only properties (cornerRadius, shadow, 3D transforms); set model value first101- iOS 17+ spring API: `UIView.animate(springDuration:bounce:)` aligns with SwiftUI102- Constraint animation: flush layout → update constant → animate `layoutIfNeeded()` on superview103104### Memory Management105- Default to `[weak self]` in all escaping closures106- Timer: use block-based API with `[weak self]`, invalidate in `viewWillDisappear`107- CADisplayLink: use weak proxy pattern (no block-based API available)108- NotificationCenter: `[weak self]` in closure, remove observer in `deinit`109- Nested closures: re-capture `[weak self]` in stored inner closures110- Delegates: always `weak var delegate: SomeDelegate?` with `AnyObject` constraint111- Verify deallocation with `deinit` — if never called, a retain cycle exists112113### Concurrency114- `UIViewController` is `@MainActor` — all subclass methods are implicitly main-actor115- Store `Task` references, cancel in `viewDidDisappear` — not `deinit`116- Check `Task.isCancelled` before UI updates after `await`117- `Task.detached` does NOT inherit actor isolation — explicit `MainActor.run` needed for UI118- Never call `DispatchQueue.main.sync` from background — use `await MainActor.run`119120### UIKit–SwiftUI Interop121- UIHostingController: full child VC containment (`addChild` → `addSubview` → `didMove`), retain as stored property122- `sizingOptions = .intrinsicContentSize` (iOS 16+) for Auto Layout containers123- UIViewRepresentable: set mutable state in `updateUIView`, not `makeUIView`; guard against update loops124- UIHostingConfiguration (iOS 16+) for SwiftUI content in collection view cells125126### Image Loading127- Decoded bitmap size = width × height × 4 bytes (a 12MP photo = ~48MB RAM)128- Downsample with ImageIO at display size — never load full bitmap and resize129- iOS 15+: use `byPreparingThumbnail(of:)` or `prepareForDisplay()` for async decoding130- Cell reuse: cancel Task in `prepareForReuse`, clear image, verify identity on completion131132### Keyboard & Scroll133- Use `UIKeyboardLayoutGuide` (iOS 15+) — pin content bottom to `view.keyboardLayoutGuide.topAnchor`134- iPad: set `followsUndockedKeyboard = true` for floating keyboards135- Replace all manual keyboard notification handling with the layout guide136137### Adaptive Layout & Accessibility138- Use `registerForTraitChanges` (iOS 17+) instead of deprecated `traitCollectionDidChange`139- Dynamic Type: `UIFont.preferredFont(forTextStyle:)` + `adjustsFontForContentSizeCategory = true`140- Dark mode: use semantic colors (`.label`, `.systemBackground`); re-resolve CGColor on trait changes141- VoiceOver: set `accessibilityLabel`, `accessibilityTraits`, `accessibilityHint` on custom views142- Use `UIAccessibilityCustomAction` for complex list item actions143144## Quick Reference145146### View Controller Lifecycle Method Selection147| Method | Use For |148|--------|---------|149| `viewDidLoad` | One-time setup: subviews, constraints, delegates |150| `viewIsAppearing` | Geometry-dependent work, trait-based layout, scroll-to-item |151| `viewWillAppear` | Transition coordinator animations only |152| `viewDidLayoutSubviews` | Lightweight layer frame adjustments (fires multiple times) |153| `viewDidAppear` | Start animations, analytics, post-appearance work |154| `viewWillDisappear` | Cancel tasks, invalidate timers, save state |155| `viewDidDisappear` | Final cleanup, cancel background work |156157### Animation API Selection158| API | Best For | Interactive | Off Main Thread |159|-----|----------|-------------|-----------------|160| `UIView.animate` | Simple one-shot changes | No | No |161| `UIViewPropertyAnimator` | Gesture-driven, interruptible | Yes | No |162| `CABasicAnimation` | Layer properties, 3D transforms | Limited | Yes (Render Server) |163164### Deprecated → Modern API Replacements165| Deprecated / Legacy | Modern Replacement | Since |166|---------------------|-------------------|-------|167| `traitCollectionDidChange` | `registerForTraitChanges(_:handler:)` | iOS 17 |168| Keyboard notifications | `UIKeyboardLayoutGuide` | iOS 15 |169| `cell.textLabel` / `detailTextLabel` | `UIListContentConfiguration` | iOS 14 |170| `register` + string dequeue | `UICollectionView.CellRegistration` | iOS 14 |171| `reloadItems` on snapshot | `reconfigureItems` | iOS 15 |172| `barTintColor` / `isTranslucent` | `UINavigationBarAppearance` (4 slots) | iOS 13 |173| `UICollectionViewFlowLayout` (complex) | `UICollectionViewCompositionalLayout` | iOS 13 |174| Manual `layoutIfNeeded()` in animations | `.flushUpdates` option | iOS 26 |175| Legacy app lifecycle | `UIScene` + `SceneDelegate` | Mandatory iOS 26 |176| `ObservableObject` + manual invalidation | `@Observable` + `UIObservationTrackingEnabled` | iOS 18 |177178## Review Checklist179180### View Controller Lifecycle181- [ ] `viewDidLoad` contains NO geometry-dependent work182- [ ] Geometry/trait work is in `viewIsAppearing`, not `viewWillAppear`183- [ ] Every lifecycle override calls `super`184- [ ] Child VC uses correct containment sequence185- [ ] `deinit` is implemented for leak verification during development186187### Auto Layout188- [ ] `translatesAutoresizingMaskIntoConstraints = false` on all programmatic views189- [ ] Constraints activated via `NSLayoutConstraint.activate([])`190- [ ] No constraint removal/recreation — using `isActive` toggle or `.constant` modification191- [ ] No priority changes from/to `.required` (1000) at runtime192- [ ] No `setNeedsLayout()` inside `layoutSubviews` or `viewDidLayoutSubviews` (infinite loop)193- [ ] Constraint identifiers set for debugging194195### Collection Views196- [ ] Using diffable data source with stable identifiers (not full model structs)197- [ ] `reconfigureItems` for content updates, not `reloadItems`198- [ ] `CellRegistration` instead of string-based register/dequeue199- [ ] `UIContentConfiguration` instead of deprecated cell properties200- [ ] No duplicate identifiers in snapshot (`BUG_IN_CLIENT` crash)201- [ ] Self-sizing cells have unambiguous top-to-bottom constraint chain202203### Navigation204- [ ] All 4 `UINavigationBarAppearance` slots configured205- [ ] Appearance set on `navigationItem` in `viewDidLoad`, not `navigationBar` in `viewWillAppear`206- [ ] Concurrent transition guard in place207- [ ] `prefersLargeTitles` set once; `largeTitleDisplayMode` per VC208209### Animation210- [ ] Correct API chosen for use case (animate vs PropertyAnimator vs CA)211- [ ] `UIViewPropertyAnimator` state machine respected212- [ ] Constraint animation uses correct pattern (flush → update → animate)213- [ ] `CAAnimation` sets model value before adding animation214- [ ] Completion handlers check `finished` parameter215216### Memory Management217- [ ] `[weak self]` in all escaping closures218- [ ] Timers use block-based API with `[weak self]`; invalidated in `viewWillDisappear`219- [ ] Task references stored and cancelled in `viewDidDisappear`220- [ ] CADisplayLink uses weak proxy pattern221- [ ] Delegates declared as `weak var` on `AnyObject`-constrained protocol222- [ ] No strong self re-capture in nested stored closures223224### Concurrency225- [ ] `Task.isCancelled` checked after `await` before UI updates226- [ ] No `Task.detached` for UI work without explicit `MainActor.run`227- [ ] No redundant `@MainActor` on `UIViewController` subclasses (already inherited)228- [ ] No `DispatchQueue.main.sync` from background229230### Image Loading231- [ ] Images downsampled to display size (not loaded at full resolution)232- [ ] Cell image loading: cancel in `prepareForReuse`, clear image, verify identity233- [ ] `NSCache` sized by decoded bitmap bytes, not file size234235### UIKit–SwiftUI Interop236- [ ] `UIHostingController` retained as stored property (not local variable)237- [ ] `UIHostingController` uses full child VC containment (`addChild` → `addSubview` → `didMove`)238- [ ] `updateUIView` guards against infinite update loops with equality checks239240### Keyboard241- [ ] Using `UIKeyboardLayoutGuide` (iOS 15+) instead of keyboard notifications242- [ ] iPad: `followsUndockedKeyboard = true` on the layout guide243244### Adaptive & Accessibility245- [ ] `registerForTraitChanges` (iOS 17+) instead of `traitCollectionDidChange`246- [ ] Dynamic Type: `preferredFont` + `adjustsFontForContentSizeCategory = true`247- [ ] CGColor properties re-resolved on trait changes (layer.borderColor, shadowColor)248- [ ] Custom views have `accessibilityLabel` and `accessibilityTraits`249- [ ] `UIAccessibilityCustomAction` for complex list item actions250251### Modern APIs (iOS 26+)252- [ ] `#available` guards with sensible fallbacks for iOS 26+ features253- [ ] `UIScene` lifecycle adopted (mandatory for iOS 26 SDK)254- [ ] `UIObservationTrackingEnabled` considered for iOS 18+ targets255256## References257- `references/view-controller-lifecycle.md` — Lifecycle ordering, viewIsAppearing, child VC containment258- `references/auto-layout.md` — Batch activation, constraint churn, priority, animation, debugging259- `references/modern-collection-views.md` — Diffable data sources, compositional layout, CellRegistration260- `references/cell-configuration.md` — UIContentConfiguration, UIBackgroundConfiguration, configurationUpdateHandler261- `references/list-performance.md` — Prefetching, cell reuse, reconfigureItems, scroll performance262- `references/navigation-patterns.md` — Bar appearance, concurrent transitions, large titles, deep links263- `references/animation-patterns.md` — UIView.animate, UIViewPropertyAnimator, CAAnimation, springs264- `references/memory-management.md` — Retain cycles, [weak self], Timer/CADisplayLink/nested closure traps265- `references/concurrency-main-thread.md` — @MainActor, Task lifecycle, Swift 6, GCD migration266- `references/uikit-swiftui-interop.md` — UIHostingController, UIViewRepresentable, sizing, state bridging267- `references/image-loading.md` — Downsampling, decoded bitmap math, cell reuse race condition268- `references/keyboard-scroll.md` — UIKeyboardLayoutGuide, scroll view insets, iPad floating keyboard269- `references/adaptive-appearance.md` — Trait changes, Dynamic Type, dark mode, VoiceOver, accessibility270- `references/modern-uikit-apis.md` — Observation framework, updateProperties(), .flushUpdates, UIScene, Liquid Glass271272## Philosophy273274This skill focuses on **facts and best practices**, not architectural opinions:275- We don't enforce specific architectures (e.g., MVVM, VIPER, Coordinator)276- We do encourage separating business logic for testability277- We optimize for correctness first, then performance278- We follow Apple's documented APIs and Human Interface Guidelines279- We use "suggest" or "consider" for optional optimizations280- We use "always" or "never" only for correctness issues