Install
$ agentstack add skill-ivan-magda-uikit-expert-skill-uikit-expert ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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 { 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()
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
Keyboard & Scroll — see references/keyboard-scroll.md
- 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 — see references/adaptive-appearance.md
- 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 — Deprecated → Modern APIs
| 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
One gate per domain — the correctness items to verify (details in the matching Core Guideline above):
- [ ] Lifecycle — no geometry in
viewDidLoad(useviewIsAppearing); every override callssuper; child VC =addChild → addSubview → didMove;deinitconfirms dealloc - [ ] Auto Layout —
translatesAutoresizingMaskIntoConstraints = false;NSLayoutConstraint.activate([]); no churn (toggleisActive/.constant); nosetNeedsLayout()insidelayoutSubviews(infinite loop) - [ ] Collection views — diffable + stable identifiers;
reconfigureItemsnotreloadItems; no duplicate snapshot IDs (BUG_IN_CLIENTcrash);CellRegistrationnot string dequeue - [ ] Navigation — all 4 appearance slots; on
navigationItemnotnavigationBar; concurrent-transition guard - [ ] Animation — API fits use case; constraint animation flush → update → animate;
CAAnimationsets model value first - [ ] Memory —
[weak self]in escaping closures;Tasks cancelled inviewDidDisappear; delegatesweak; no strong-self recapture in nested closures - [ ] Concurrency —
Task.isCancelledchecked afterawait; noDispatchQueue.main.syncfrom background; no redundant@MainActoron VC subclasses - [ ] Images — downsampled to display size; cell load cancels in
prepareForReuse+ verifies identity;NSCachesized by decoded bytes - [ ] Interop —
UIHostingControllerretained + full containment;updateUIViewguards update loops - [ ] Keyboard —
UIKeyboardLayoutGuide, not notifications - [ ] Adaptive/A11y —
registerForTraitChanges; Dynamic Type; CGColor re-resolved on trait change;accessibilityLabel/Traitson custom views - [ ] iOS 26+ —
#availableguards with fallbacks;UIScenelifecycle; considerUIObservationTrackingEnabled
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
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ivan-magda
- Source: ivan-magda/uikit-expert-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.