uikit-expert
SKILL.md
UIKit Expert Skill
Overview
Use 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).
Workflow Decision Tree
1) Review existing UIKit code
- Check view controller lifecycle usage —
viewIsAppearingfor geometry,viewDidLoadfor setup only (seereferences/view-controller-lifecycle.md) - Verify Auto Layout correctness — batch activation, no constraint churn,
translatesAutoresizingMaskIntoConstraints(seereferences/auto-layout.md) - Check collection/table view APIs — diffable data sources, stable identity, CellRegistration (see
references/modern-collection-views.md) - Verify cell configuration uses
UIContentConfiguration, not deprecatedtextLabel(seereferences/cell-configuration.md) - Check list scroll performance — prefetching, cell reuse cleanup, reconfigureItems (see
references/list-performance.md) - Verify navigation patterns — bar appearance all 4 slots, no concurrent transition crashes (see
references/navigation-patterns.md) - Check animation correctness — API selection, PropertyAnimator state machine, constraint animation (see
references/animation-patterns.md) - Audit memory management —
[weak self], delegate ownership, Timer/CADisplayLink traps (seereferences/memory-management.md) - Check concurrency safety — Task lifecycle, cancellation in viewDidDisappear (see
references/concurrency-main-thread.md) - If SwiftUI interop present — verify UIHostingController containment, sizingOptions (see
references/uikit-swiftui-interop.md) - Check image loading — downsampling, cell reuse race condition (cancel/clear/verify) (see
references/image-loading.md) - Verify keyboard handling — UIKeyboardLayoutGuide over manual notifications (see
references/keyboard-scroll.md) - Check trait handling and accessibility — registerForTraitChanges, Dynamic Type, VoiceOver (see
references/adaptive-appearance.md) - Validate modern API adoption and iOS 26+ availability handling (see
references/modern-uikit-apis.md)
2) Improve existing UIKit code
- Replace geometry work in
viewDidLoadwithviewIsAppearing(seereferences/view-controller-lifecycle.md) - Eliminate constraint churn — create once, toggle
isActiveor modify.constant(seereferences/auto-layout.md) - Migrate from legacy
UITableViewDataSourceto diffable data sources (seereferences/modern-collection-views.md) - Replace deprecated
textLabel/detailTextLabel/imageViewwithUIContentConfiguration(seereferences/cell-configuration.md) - Replace
reloadItemswithreconfigureItemsfor in-place cell updates (seereferences/list-performance.md) - Fix navigation bar appearance — set all 4 appearance slots, use
navigationItemnotnavigationBar(seereferences/navigation-patterns.md) - Improve animations — use PropertyAnimator for gestures, correct constraint animation pattern (see
references/animation-patterns.md) - Fix retain cycles — add
[weak self], cancel Tasks inviewDidDisappear, use block-based Timer (seereferences/memory-management.md) - Migrate GCD to Swift concurrency — replace
DispatchQueue.main.asyncwithTask(seereferences/concurrency-main-thread.md) - Suggest image downsampling when
UIImage(data:)or full-resolution loading detected (as optional optimization, seereferences/image-loading.md) - Replace keyboard notification handling with
UIKeyboardLayoutGuide(seereferences/keyboard-scroll.md) - Replace
traitCollectionDidChangewithregisterForTraitChanges(seereferences/adaptive-appearance.md) - Adopt iOS 26 APIs where appropriate — Observation, updateProperties(), .flushUpdates (see
references/modern-uikit-apis.md)
3) Implement new UIKit feature
- Design data flow first: identify owned state, injected dependencies, and model layer
- Set up view controller lifecycle correctly — one-time setup in
viewDidLoad, geometry inviewIsAppearing(seereferences/view-controller-lifecycle.md) - Build Auto Layout with batch activation and zero churn (see
references/auto-layout.md) - Use modern collection view stack: DiffableDataSource + CompositionalLayout + CellRegistration (see
references/modern-collection-views.md) - Configure cells with
UIContentConfigurationandconfigurationUpdateHandler(seereferences/cell-configuration.md) - Implement prefetching and proper cell reuse cleanup for lists (see
references/list-performance.md) - Set up navigation with all 4 appearance slots and concurrent-transition guards (see
references/navigation-patterns.md) - Choose correct animation API for the use case (see
references/animation-patterns.md) - Use
[weak self]in escaping closures, cancel Tasks in lifecycle methods (seereferences/memory-management.md) - Use
@MainActorcorrectly, store Task references (seereferences/concurrency-main-thread.md) - If embedding SwiftUI — use full child VC containment for UIHostingController (see
references/uikit-swiftui-interop.md) - Downsample images for display, handle cell reuse race condition (see
references/image-loading.md) - Use
UIKeyboardLayoutGuidefor keyboard handling (seereferences/keyboard-scroll.md) - Support Dynamic Type, VoiceOver, dark mode from the start (see
references/adaptive-appearance.md) - Gate iOS 26+ features with
#availableand provide sensible fallbacks (seereferences/modern-uikit-apis.md)
Core Guidelines
View Controller Lifecycle
- Use
viewDidLoadfor one-time setup: subviews, constraints, delegates — NOT geometry - Use
viewIsAppearing(back-deployed iOS 13+) for geometry-dependent work, trait-based layout, scroll-to-item viewDidLayoutSubviewsfires multiple times — use only for lightweight layer frame adjustmentsviewWillAppearis limited to transition coordinator animations and balanced notification registration- Always call
superin every lifecycle override - Child VC containment:
addChild→addSubview→didMove(toParent:)— in that exact order - Verify deallocation with
deinitlogging during development
Auto Layout
- Always set
translatesAutoresizingMaskIntoConstraints = falseon programmatic views - Use
NSLayoutConstraint.activate([])— never individual.isActive = true - Create constraints once, toggle
isActiveor modify.constant— never remove and recreate - Never change priority from/to
.required(1000) at runtime — use 999 - Animate constraints: update constant → call
layoutIfNeeded()inside animation block on superview - iOS 26+: use
.flushUpdatesoption to simplify constraint animation - Avoid deeply nested UIStackViews in reusable cells
Collection Views & Data Sources
- Use
UICollectionViewDiffableDataSourcewith stable identifiers (UUID/database ID, not full model structs) - Use
reconfigureItemsfor content updates,reloadItemsonly when cell type changes - Use
applySnapshotUsingReloadDatafor initial population (bypasses diffing) - Use
UICollectionViewCompositionalLayoutfor any non-trivial layout - Use
UICollectionView.CellRegistration— no string identifiers, no manual casting - Use
UIContentConfigurationfor cell content andUIBackgroundConfigurationfor cell backgrounds - Use
configurationUpdateHandlerfor state-driven styling (selection, highlight)
Navigation
- Configure all 4
UINavigationBarAppearanceslots (standard, scrollEdge, compact, compactScrollEdge) - Set appearance on
navigationItem(per-VC) inviewDidLoad, not onnavigationBarinviewWillAppear - Use
setViewControllers(_:animated:)for deep links — not sequential push calls - Guard against concurrent transitions — check
transitionCoordinatorbefore push/pop - Set
prefersLargeTitlesonce on the bar; uselargeTitleDisplayModeper VC
Animation
UIView.animate— simple one-shot animations; checkfinishedin completionUIViewPropertyAnimator— gesture-driven, interruptible; respect state machine (inactive → active → stopped)CABasicAnimation— layer-only properties (cornerRadius, shadow, 3D transforms); set model value first- iOS 17+ spring API:
UIView.animate(springDuration:bounce:)aligns with SwiftUI - Constraint animation: flush layout → update constant → animate
layoutIfNeeded()on superview
Memory Management
- Default to
[weak self]in all escaping closures - Timer: use block-based API with
[weak self], invalidate inviewWillDisappear - CADisplayLink: use weak proxy pattern (no block-based API available)
- NotificationCenter:
[weak self]in closure, remove observer indeinit - Nested closures: re-capture
[weak self]in stored inner closures - Delegates: always
weak var delegate: SomeDelegate?withAnyObjectconstraint - Verify deallocation with
deinit— if never called, a retain cycle exists
Concurrency
UIViewControlleris@MainActor— all subclass methods are implicitly main-actor- Store
Taskreferences, cancel inviewDidDisappear— notdeinit - Check
Task.isCancelledbefore UI updates afterawait Task.detacheddoes NOT inherit actor isolation — explicitMainActor.runneeded for UI- Never call
DispatchQueue.main.syncfrom background — useawait MainActor.run
UIKit–SwiftUI Interop
- UIHostingController: full child VC containment (
addChild→addSubview→didMove), retain as stored property sizingOptions = .intrinsicContentSize(iOS 16+) for Auto Layout containers- UIViewRepresentable: set mutable state in
updateUIView, notmakeUIView; guard against update loops - UIHostingConfiguration (iOS 16+) for SwiftUI content in collection view cells
Image Loading
- Decoded bitmap size = width × height × 4 bytes (a 12MP photo = ~48MB RAM)
- Downsample with ImageIO at display size — never load full bitmap and resize
- iOS 15+: use
byPreparingThumbnail(of:)orprepareForDisplay()for async decoding - Cell reuse: cancel Task in
prepareForReuse, clear image, verify identity on completion
Keyboard & Scroll
- Use
UIKeyboardLayoutGuide(iOS 15+) — pin content bottom toview.keyboardLayoutGuide.topAnchor - iPad: set
followsUndockedKeyboard = truefor floating keyboards - Replace all manual keyboard notification handling with the layout guide
Adaptive Layout & Accessibility
- Use
registerForTraitChanges(iOS 17+) instead of deprecatedtraitCollectionDidChange - Dynamic Type:
UIFont.preferredFont(forTextStyle:)+adjustsFontForContentSizeCategory = true - Dark mode: use semantic colors (
.label,.systemBackground); re-resolve CGColor on trait changes - VoiceOver: set
accessibilityLabel,accessibilityTraits,accessibilityHinton custom views - Use
UIAccessibilityCustomActionfor complex list item actions
Quick Reference
View Controller Lifecycle Method Selection
| Method | Use For |
|---|---|
viewDidLoad |
One-time setup: subviews, constraints, delegates |
viewIsAppearing |
Geometry-dependent work, trait-based layout, scroll-to-item |
viewWillAppear |
Transition coordinator animations only |
viewDidLayoutSubviews |
Lightweight layer frame adjustments (fires multiple times) |
viewDidAppear |
Start animations, analytics, post-appearance work |
viewWillDisappear |
Cancel tasks, invalidate timers, save state |
viewDidDisappear |
Final cleanup, cancel background work |
Animation API Selection
| API | Best For | Interactive | Off Main Thread |
|---|---|---|---|
UIView.animate |
Simple one-shot changes | No | No |
UIViewPropertyAnimator |
Gesture-driven, interruptible | Yes | No |
CABasicAnimation |
Layer properties, 3D transforms | Limited | Yes (Render Server) |
Deprecated → Modern API Replacements
| Deprecated / Legacy | Modern Replacement | Since |
|---|---|---|
traitCollectionDidChange |
registerForTraitChanges(_:handler:) |
iOS 17 |
| Keyboard notifications | UIKeyboardLayoutGuide |
iOS 15 |
cell.textLabel / detailTextLabel |
UIListContentConfiguration |
iOS 14 |
register + string dequeue |
UICollectionView.CellRegistration |
iOS 14 |
reloadItems on snapshot |
reconfigureItems |
iOS 15 |
barTintColor / isTranslucent |
UINavigationBarAppearance (4 slots) |
iOS 13 |
UICollectionViewFlowLayout (complex) |
UICollectionViewCompositionalLayout |
iOS 13 |
Manual layoutIfNeeded() in animations |
.flushUpdates option |
iOS 26 |
| Legacy app lifecycle | UIScene + SceneDelegate |
Mandatory iOS 26 |
ObservableObject + manual invalidation |
@Observable + UIObservationTrackingEnabled |
iOS 18 |
Review Checklist
View Controller Lifecycle
-
viewDidLoadcontains NO geometry-dependent work - Geometry/trait work is in
viewIsAppearing, notviewWillAppear - Every lifecycle override calls
super - Child VC uses correct containment sequence
-
deinitis implemented for leak verification during development
Auto Layout
-
translatesAutoresizingMaskIntoConstraints = falseon all programmatic views - Constraints activated via
NSLayoutConstraint.activate([]) - No constraint removal/recreation — using
isActivetoggle or.constantmodification - No priority changes from/to
.required(1000) at runtime - No
setNeedsLayout()insidelayoutSubviewsorviewDidLayoutSubviews(infinite loop) - Constraint identifiers set for debugging
Collection Views
- Using diffable data source with stable identifiers (not full model structs)
-
reconfigureItemsfor content updates, notreloadItems -
CellRegistrationinstead of string-based register/dequeue -
UIContentConfigurationinstead of deprecated cell properties - No duplicate identifiers in snapshot (
BUG_IN_CLIENTcrash) - Self-sizing cells have unambiguous top-to-bottom constraint chain
Navigation
- All 4
UINavigationBarAppearanceslots configured - Appearance set on
navigationIteminviewDidLoad, notnavigationBarinviewWillAppear - Concurrent transition guard in place
-
prefersLargeTitlesset once;largeTitleDisplayModeper VC
Animation
- Correct API chosen for use case (animate vs PropertyAnimator vs CA)
-
UIViewPropertyAnimatorstate machine respected - Constraint animation uses correct pattern (flush → update → animate)
-
CAAnimationsets model value before adding animation - Completion handlers check
finishedparameter
Memory Management
-
[weak self]in all escaping closures - Timers use block-based API with
[weak self]; invalidated inviewWillDisappear - Task references stored and cancelled in
viewDidDisappear - CADisplayLink uses weak proxy pattern
- Delegates declared as
weak varonAnyObject-constrained protocol - No strong self re-capture in nested stored closures
Concurrency
-
Task.isCancelledchecked afterawaitbefore UI updates - No
Task.detachedfor UI work without explicitMainActor.run - No redundant
@MainActoronUIViewControllersubclasses (already inherited) - No
DispatchQueue.main.syncfrom background
Image Loading
- Images downsampled to display size (not loaded at full resolution)
- Cell image loading: cancel in
prepareForReuse, clear image, verify identity -
NSCachesized by decoded bitmap bytes, not file size
UIKit–SwiftUI Interop
-
UIHostingControllerretained as stored property (not local variable) -
UIHostingControlleruses full child VC containment (addChild→addSubview→didMove) -
updateUIViewguards against infinite update loops with equality checks
Keyboard
- Using
UIKeyboardLayoutGuide(iOS 15+) instead of keyboard notifications - iPad:
followsUndockedKeyboard = trueon the layout guide
Adaptive & Accessibility
-
registerForTraitChanges(iOS 17+) instead oftraitCollectionDidChange - Dynamic Type:
preferredFont+adjustsFontForContentSizeCategory = true - CGColor properties re-resolved on trait changes (layer.borderColor, shadowColor)
- Custom views have
accessibilityLabelandaccessibilityTraits -
UIAccessibilityCustomActionfor complex list item actions
Modern APIs (iOS 26+)
-
#availableguards with sensible fallbacks for iOS 26+ features -
UIScenelifecycle adopted (mandatory for iOS 26 SDK) -
UIObservationTrackingEnabledconsidered for iOS 18+ targets
References
references/view-controller-lifecycle.md— Lifecycle ordering, viewIsAppearing, child VC containmentreferences/auto-layout.md— Batch activation, constraint churn, priority, animation, debuggingreferences/modern-collection-views.md— Diffable data sources, compositional layout, CellRegistrationreferences/cell-configuration.md— UIContentConfiguration, UIBackgroundConfiguration, configurationUpdateHandlerreferences/list-performance.md— Prefetching, cell reuse, reconfigureItems, scroll performancereferences/navigation-patterns.md— Bar appearance, concurrent transitions, large titles, deep linksreferences/animation-patterns.md— UIView.animate, UIViewPropertyAnimator, CAAnimation, springsreferences/memory-management.md— Retain cycles, [weak self], Timer/CADisplayLink/nested closure trapsreferences/concurrency-main-thread.md— @MainActor, Task lifecycle, Swift 6, GCD migrationreferences/uikit-swiftui-interop.md— UIHostingController, UIViewRepresentable, sizing, state bridgingreferences/image-loading.md— Downsampling, decoded bitmap math, cell reuse race conditionreferences/keyboard-scroll.md— UIKeyboardLayoutGuide, scroll view insets, iPad floating keyboardreferences/adaptive-appearance.md— Trait changes, Dynamic Type, dark mode, VoiceOver, accessibilityreferences/modern-uikit-apis.md— Observation framework, updateProperties(), .flushUpdates, UIScene, Liquid Glass
Philosophy
This skill focuses on facts and best practices, not architectural opinions:
- We don't enforce specific architectures (e.g., MVVM, VIPER, Coordinator)
- We do encourage separating business logic for testability
- We optimize for correctness first, then performance
- We follow Apple's documented APIs and Human Interface Guidelines
- We use "suggest" or "consider" for optional optimizations
- We use "always" or "never" only for correctness issues
Weekly Installs
22
Repository
ivan-magda/uiki…rt-skillGitHub Stars
1
First Seen
Feb 27, 2026
Security Audits
Installed on
opencode17
gemini-cli17
amp17
cline17
github-copilot17
codex17