Uikit Expert
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.
- Skill ID
- ivan-magda/uikit-expert-skill/uikit-expert
- Publisher
- ivan-magda
- Repository
- uikit-expert-skill
- Installs
- 0
- Files
- 16
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
ivan-magda/uikit-expert-skill/uikit-expertInstalls these files- .tessl-plugin/plugin.json
- SKILL.md
- references/adaptive-appearance.md
- references/animation-patterns.md
- references/auto-layout.md
- references/cell-configuration.md
- references/concurrency-main-thread.md
- references/image-loading.md
- references/keyboard-scroll.md
- references/list-performance.md
- references/memory-management.md
- references/modern-collection-views.md
- references/modern-uikit-apis.md
- references/navigation-patterns.md
- references/uikit-swiftui-interop.md
- references/view-controller-lifecycle.md
What this skill tells the agent
UIKit Expert Skill
Overview
Favor native APIs and Apple's documented guidance. Operating principles: facts over architecture (no MVVM/VIPER/Coordinator mandates, but do separate business logic for testability); correctness first, then performance; reserve "always"/"never" for correctness, use "suggest"/"consider" for optional optimizations.
Workflows
All three modes draw on the same Core Guidelines below; each topic links to its reference file there. Pick the entry point:
- Review — Walk the Review Checklist as pass/fail gates. For any failure, fix per the matching Core Guideline. Prioritize correctness gates (lifecycle, Auto Layout, memory, concurrency) over style; treat performance items as optional.
- Improve — For each issue, apply the modern replacement from the Deprecated → Modern table and the matching Core Guideline. Present performance work (downsampling, prefetching, constraint-churn fixes) as optional optimizations, never mandates.
- Implement new — Build in this order, applying the relevant Core Guideline at each step:
- Design data flow — owned state, injected dependencies, model layer
- Lifecycle — one-time setup in
viewDidLoad, geometry inviewIsAppearing - Auto Layout — batch activation, zero churn
- Collection views — DiffableDataSource + CompositionalLayout + CellRegistration
- Navigation — all 4 appearance slots, concurrent-transition guards
- Animation, memory (
[weak self], Task cancellation), concurrency (@MainActor) - Interop, image loading, keyboard, Dynamic Type / VoiceOver / dark mode
- Gate iOS 26+ features with
#availableand sensible fallbacks
Core Guidelines
View Controller Lifecycle — see references/view-controller-lifecycle.md
- 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 — see references/auto-layout.md
- 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
- Set
constraint.identifieron key constraints soUnable to simultaneously satisfy constraintslogs are readable
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false // required on programmatic views
view.addSubview(label)
NSLayoutConstraint.activate([ // one batch = one solve pass
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])Collection Views & Data Sources — see references/modern-collection-views.md, references/cell-configuration.md, references/list-performance.md
- 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)
// Store the registration once — never create it inside the cell provider (crashes on iOS 15+).
private lazy var cellRegistration = UICollectionView.CellRegistration<PhotoCell, Photo> { cell, _, photo in
cell.configure(with: photo)
}
// Data source is keyed by the stable ID, so the provider receives an id — look up the model, then configure with it.
dataSource = .init(collectionView: collectionView) { [weak self] cv, indexPath, id in
guard let self, let photo = self.photo(for: id) else { return nil }
return cv.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: photo)
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Photo.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(photos.map(\.id))
dataSource.apply(snapshot, animatingDifferences: true)Navigation — see references/navigation-patterns.md
- 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 — see references/animation-patterns.md
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 — see references/memory-management.md
- 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 — see references/concurrency-main-thread.md
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 — see references/uikit-swiftui-interop.md
- 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 — see references/image-loading.md
- 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
More from ivan-magda
Use this skill in a project
Open any RiverX project, search the Skills panel for ivan-magda/uikit-expert-skill/uikit-expert, and install it in one click.
