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.
96
100%
Does it follow best practices?
Impact
96%
1.23xAverage score across 9 eval scenarios
Passed
No known issues
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.
All three modes draw on the same Core Guidelines below; each topic links to its reference file there. Pick the entry point:
viewDidLoad, geometry in viewIsAppearing[weak self], Task cancellation), concurrency (@MainActor)#available and sensible fallbacksreferences/view-controller-lifecycle.mdviewDidLoad for one-time setup: subviews, constraints, delegates — NOT geometryviewIsAppearing (back-deployed iOS 13+) for geometry-dependent work, trait-based layout, scroll-to-itemviewDidLayoutSubviews fires multiple times — use only for lightweight layer frame adjustmentsviewWillAppear is limited to transition coordinator animations and balanced notification registrationsuper in every lifecycle overrideaddChild → addSubview → didMove(toParent:) — in that exact orderdeinit logging during developmentreferences/auto-layout.mdtranslatesAutoresizingMaskIntoConstraints = false on programmatic viewsNSLayoutConstraint.activate([]) — never individual .isActive = trueisActive or modify .constant — never remove and recreate.required (1000) at runtime — use 999layoutIfNeeded() inside animation block on superview.flushUpdates option to simplify constraint animationconstraint.identifier on key constraints so Unable to simultaneously satisfy constraints logs are readablelet 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),
])references/modern-collection-views.md, references/cell-configuration.md, references/list-performance.mdUICollectionViewDiffableDataSource with stable identifiers (UUID/database ID, not full model structs)reconfigureItems for content updates, reloadItems only when cell type changesapplySnapshotUsingReloadData for initial population (bypasses diffing)UICollectionViewCompositionalLayout for any non-trivial layoutUICollectionView.CellRegistration — no string identifiers, no manual castingUIContentConfiguration for cell content and UIBackgroundConfiguration for cell backgroundsconfigurationUpdateHandler for 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)references/navigation-patterns.mdUINavigationBarAppearance slots (standard, scrollEdge, compact, compactScrollEdge)navigationItem (per-VC) in viewDidLoad, not on navigationBar in viewWillAppearsetViewControllers(_:animated:) for deep links — not sequential push callstransitionCoordinator before push/popprefersLargeTitles once on the bar; use largeTitleDisplayMode per VCreferences/animation-patterns.mdUIView.animate — simple one-shot animations; check finished in completionUIViewPropertyAnimator — gesture-driven, interruptible; respect state machine (inactive → active → stopped)CABasicAnimation — layer-only properties (cornerRadius, shadow, 3D transforms); set model value firstUIView.animate(springDuration:bounce:) aligns with SwiftUIlayoutIfNeeded() on superviewreferences/memory-management.md[weak self] in all escaping closures[weak self], invalidate in viewWillDisappear[weak self] in closure, remove observer in deinit[weak self] in stored inner closuresweak var delegate: SomeDelegate? with AnyObject constraintdeinit — if never called, a retain cycle existsreferences/concurrency-main-thread.mdUIViewController is @MainActor — all subclass methods are implicitly main-actorTask references, cancel in viewDidDisappear — not deinitTask.isCancelled before UI updates after awaitTask.detached does NOT inherit actor isolation — explicit MainActor.run needed for UIDispatchQueue.main.sync from background — use await MainActor.runreferences/uikit-swiftui-interop.mdaddChild → addSubview → didMove), retain as stored propertysizingOptions = .intrinsicContentSize (iOS 16+) for Auto Layout containersupdateUIView, not makeUIView; guard against update loopsreferences/image-loading.mdbyPreparingThumbnail(of:) or prepareForDisplay() for async decodingprepareForReuse, clear image, verify identity on completionreferences/keyboard-scroll.mdUIKeyboardLayoutGuide (iOS 15+) — pin content bottom to view.keyboardLayoutGuide.topAnchorfollowsUndockedKeyboard = true for floating keyboardsreferences/adaptive-appearance.mdregisterForTraitChanges (iOS 17+) instead of deprecated traitCollectionDidChangeUIFont.preferredFont(forTextStyle:) + adjustsFontForContentSizeCategory = true.label, .systemBackground); re-resolve CGColor on trait changesaccessibilityLabel, accessibilityTraits, accessibilityHint on custom viewsUIAccessibilityCustomAction for complex list item actions| 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 |
One gate per domain — the correctness items to verify (details in the matching Core Guideline above):
viewDidLoad (use viewIsAppearing); every override calls super; child VC = addChild → addSubview → didMove; deinit confirms dealloctranslatesAutoresizingMaskIntoConstraints = false; NSLayoutConstraint.activate([]); no churn (toggle isActive/.constant); no setNeedsLayout() inside layoutSubviews (infinite loop)reconfigureItems not reloadItems; no duplicate snapshot IDs (BUG_IN_CLIENT crash); CellRegistration not string dequeuenavigationItem not navigationBar; concurrent-transition guardCAAnimation sets model value first[weak self] in escaping closures; Tasks cancelled in viewDidDisappear; delegates weak; no strong-self recapture in nested closuresTask.isCancelled checked after await; no DispatchQueue.main.sync from background; no redundant @MainActor on VC subclassesprepareForReuse + verifies identity; NSCache sized by decoded bytesUIHostingController retained + full containment; updateUIView guards update loopsUIKeyboardLayoutGuide, not notificationsregisterForTraitChanges; Dynamic Type; CGColor re-resolved on trait change; accessibilityLabel/Traits on custom views#available guards with fallbacks; UIScene lifecycle; consider UIObservationTrackingEnabledreferences/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