iOS HIG Review
Purpose
Audit an iOS interface (UIKit or SwiftUI) against Apple's Human Interface Guidelines: safe-area layout, Dynamic Type, SF Symbols usage, haptic feedback, dark mode, tap-target sizing, navigation paradigm, and VoiceOver support. The output is a prioritized set of findings, each citing the relevant HIG area and a concrete code fix, ranked by App Store submission risk. The review reads source or screenshots only and never triggers Xcode builds or TestFlight uploads.
When to use
- A SwiftUI or UIKit screen needs review before App Store submission.
- Design or code review for an iOS-specific feature (sheets, navigation, widgets, Live Activities).
- An App Store rejection cited HIG violations and a root-cause analysis is needed.
- An accessibility audit is requested for VoiceOver, Dynamic Type, or Reduce Motion compliance.
When not to use
- The app is Android-only — use
android-material-review.
- The review is purely about business logic or backend integration — use
mobile-app-review.
- The design exists in a design tool only with no code to audit — this skill needs source or screenshots.
Procedure
- Identify the UI framework and deployment target. Confirm SwiftUI, UIKit, or hybrid. Note the minimum iOS version — HIG expectations differ across iOS 15 to 18 (for example NavigationStack and the Dynamic Island land in iOS 16 and later).
- Check safe area and layout margins. Verify
.safeAreaInset, safeAreaLayoutGuide, or .ignoresSafeArea usage is intentional. Content under the notch, Dynamic Island, or home indicator must be deliberate and remain readable.
- Audit Dynamic Type support. All text should use a text style —
.font(.body) or .font(.headline) in SwiftUI, or UIFont.preferredFont(forTextStyle:) in UIKit, or a custom font via relativeTo:. Confirm labels do not truncate at accessibility sizes; use .fixedSize(horizontal: false, vertical: true) where needed.
- Review tap-target sizes. Every interactive element must be at least 44x44 pt. Wrap small icon-only buttons in
.frame(minWidth: 44, minHeight: 44) or add .contentShape(Rectangle()) to extend the hit area.
- Check SF Symbols usage. Verify symbol names exist on the deployment target (confirm in the SF Symbols app). Prefer
.symbolRenderingMode and .foregroundStyle over tint hacks; multicolor symbols need .renderingMode(.original); tab-bar symbols need the .fill variant.
- Review haptic feedback.
UIImpactFeedbackGenerator and UINotificationFeedbackGenerator (or SwiftUI .sensoryFeedback) should mark meaningful state changes, not decorate animations. Call .prepare() before .impactOccurred() to avoid first-fire latency.
- Audit dark mode support. Replace hardcoded
UIColor(red:...) and Color(red:...) with semantic colors (Color(.systemBackground), .label, .secondaryLabel). Confirm asset-catalog images have dark variants.
- Review the navigation paradigm. Use
NavigationStack (iOS 16 and later) instead of the deprecated NavigationView. Modal sheets are for self-contained tasks, not drill-down. The back-button label should reflect the parent title, not a generic "Back".
- Check sheet and presentation style. Confirm
.sheet, .fullScreenCover, and .presentationDetents are appropriate; destructive actions in a sheet require explicit confirmation.
- Review accessibility annotations. Confirm
.accessibilityLabel, .accessibilityHint, and .accessibilityValue on custom controls; mark decorative images .accessibilityHidden(true); verify UIAccessibility.isReduceMotionEnabled is respected for non-essential animation.
- Summarize findings with HIG section references and severity.
Concrete checks
Layout and type:
- Content does not underlap the notch, Dynamic Island, or home indicator unintentionally.
- All text uses a text style and scales with Dynamic Type.
- No truncation at the largest accessibility sizes.
Touch and symbols:
- Every interactive element is at least 44x44 pt.
- SF Symbol names are valid for the deployment target with the correct rendering mode.
- Tab-bar symbols use the
.fill variant.
Color and motion:
- No hardcoded colors that break dark mode; asset catalog has dark variants.
- Haptics mark meaningful feedback and call
.prepare() first.
- Non-essential animation respects Reduce Motion.
Navigation and accessibility:
NavigationStack is used on iOS 16 and later; modals are for tasks, not drill-down.
- The back-button label reflects the parent screen.
- Custom controls have VoiceOver labels and hints; decorative images are hidden.
Info.plist privacy usage strings are present and accurate.
Commands
# --- Dynamic Type ---
# hardcoded fonts that break Dynamic Type
rg -n 'Font.system\(size:|UIFont.systemFont\(ofSize:|\.font\(.system\(size:' .
# --- dark mode ---
# hardcoded colors that break dark mode
rg -n 'UIColor\(red:|Color\(red:|UIColor\(white:|#[0-9a-fA-F]{6}' .
# --- navigation ---
# deprecated NavigationView (should be NavigationStack on iOS 16+)
rg -n 'NavigationView\b' .
# custom back buttons that may drop the parent title
rg -n 'navigationBarBackButtonHidden|\.backButtonTitle' .
# --- SF Symbols / tap targets ---
# small icon-only buttons — verify 44pt hit area nearby
rg -n 'Image\(systemName:' . | head -40
# tab-bar symbols missing the fill variant
rg -n 'TabItem|tabItem' . | head
# --- haptics ---
# haptic generators and whether prepare() is called
rg -n 'FeedbackGenerator|impactOccurred|sensoryFeedback|\.prepare\(\)' . | head
# --- accessibility ---
# accessibility annotations present on custom controls?
rg -n 'accessibilityLabel|accessibilityHint|accessibilityHidden' . | head
# Reduce Motion respected?
rg -n 'isReduceMotionEnabled|accessibilityReduceMotion' .
# --- privacy ---
# Info.plist privacy usage strings
rg -n 'NSCameraUsageDescription|NSMicrophoneUsageDescription|NSLocationWhenInUseUsageDescription' .
# --- sheets / presentation ---
# sheet, full-screen cover, and detents usage
rg -n '\.sheet|fullScreenCover|presentationDetents' . | head
# --- alerts ---
# UIKit alerts presented from SwiftUI (sheet-stacking bug on iOS 17+)
rg -n 'UIAlertController|keyWindow' . | head
# --- truncation at large sizes ---
# labels that may truncate under accessibility Dynamic Type sizes
rg -n 'lineLimit\(|truncationMode|fixedSize' . | head
Common issues & anti-patterns
- Ignoring safe area globally:
.ignoresSafeArea() on a scroll view hides content under the Dynamic Island on iPhone 14 Pro and later.
- Hardcoded font sizes:
Font.system(size: 14) does not scale with Dynamic Type — use .body, .caption, .footnote, or relativeTo:.
- Custom back button without a title: stripping the back-button label breaks conventions and confuses VoiceOver users.
- UIKit alert from SwiftUI: presenting a
UIAlertController via UIApplication.shared.keyWindow breaks sheet stacking on iOS 17 and later. Use the .alert modifier.
- Missing
.symbolVariant: Image(systemName: "heart") in a tab bar without .fill violates tab-bar conventions.
- Non-semantic colors in widgets: WidgetKit ignores dynamic colors unless
Color(.widgetBackground) and .widgetAccentable() are used.
- Blocking the main thread during scroll: synchronous image loading in
cellForRowAt causes scroll jank — load asynchronously.
- Decorative haptics: firing impact feedback on every minor animation, which feels noisy and trains users to ignore it.
- Sheet for navigation: using a modal
.sheet to push a detail screen that should be a NavigationStack destination, so the back gesture and title behavior feel wrong.
- Fixed-size icon button: a 20pt icon in a 24pt frame used as a tappable button, well under the 44pt minimum and hard to hit reliably.
- No confirmation on destructive sheet: a delete action inside a sheet that fires immediately on tap with no confirmation, risking accidental data loss.
- Light-only asset: an image asset with no dark-mode variant in the catalog, so a logo or illustration glows on a dark background.
- Hardcoded accent color: a brand color set as a literal
Color(red:...) instead of an asset-catalog color set, so it ignores both dark mode and high-contrast accessibility settings.
- VoiceOver order scrambled: a custom layout where the visual order and the accessibility traversal order diverge, so VoiceOver reads controls in a confusing sequence.
Required output
Return a structured report with:
- Framework and iOS version summary.
- Findings table: Screen/Component, HIG Section, Severity, Issue, Fix.
- VoiceOver and Dynamic Type pass/fail summary.
- Top three must-fix items before App Store submission.
Safety
- Do not alter
Info.plist privacy strings without listing every change explicitly for the user to review.
- Do not trigger Xcode builds or TestFlight uploads.
- Do not read or reproduce entitlement files or provisioning-profile details.
- Redact any signing identity or team-ID values surfaced during review.
- Treat accessibility and privacy gaps as submission blockers, not cosmetic notes.
Completion criteria
Done means the framework and deployment target are identified; safe area, Dynamic Type, tap targets, SF Symbols, haptics, dark mode, navigation, and VoiceOver were each checked against HIG; every finding cites a HIG area and a concrete fix; and the top three submission blockers are called out with a VoiceOver and Dynamic Type pass/fail summary.
1---2name: ios-hig-review3description: Use when you need to review iOS interfaces against platform conventions, navigation, accessibility, and touch ergonomics.4---56# iOS HIG Review78## Purpose910Audit an iOS interface (UIKit or SwiftUI) against Apple's Human Interface Guidelines: safe-area layout, Dynamic Type, SF Symbols usage, haptic feedback, dark mode, tap-target sizing, navigation paradigm, and VoiceOver support. The output is a prioritized set of findings, each citing the relevant HIG area and a concrete code fix, ranked by App Store submission risk. The review reads source or screenshots only and never triggers Xcode builds or TestFlight uploads.1112## When to use1314- A SwiftUI or UIKit screen needs review before App Store submission.15- Design or code review for an iOS-specific feature (sheets, navigation, widgets, Live Activities).16- An App Store rejection cited HIG violations and a root-cause analysis is needed.17- An accessibility audit is requested for VoiceOver, Dynamic Type, or Reduce Motion compliance.1819## When not to use2021- The app is Android-only — use `android-material-review`.22- The review is purely about business logic or backend integration — use `mobile-app-review`.23- The design exists in a design tool only with no code to audit — this skill needs source or screenshots.2425## Procedure26271. **Identify the UI framework and deployment target.** Confirm SwiftUI, UIKit, or hybrid. Note the minimum iOS version — HIG expectations differ across iOS 15 to 18 (for example NavigationStack and the Dynamic Island land in iOS 16 and later).282. **Check safe area and layout margins.** Verify `.safeAreaInset`, `safeAreaLayoutGuide`, or `.ignoresSafeArea` usage is intentional. Content under the notch, Dynamic Island, or home indicator must be deliberate and remain readable.293. **Audit Dynamic Type support.** All text should use a text style — `.font(.body)` or `.font(.headline)` in SwiftUI, or `UIFont.preferredFont(forTextStyle:)` in UIKit, or a custom font via `relativeTo:`. Confirm labels do not truncate at accessibility sizes; use `.fixedSize(horizontal: false, vertical: true)` where needed.304. **Review tap-target sizes.** Every interactive element must be at least 44x44 pt. Wrap small icon-only buttons in `.frame(minWidth: 44, minHeight: 44)` or add `.contentShape(Rectangle())` to extend the hit area.315. **Check SF Symbols usage.** Verify symbol names exist on the deployment target (confirm in the SF Symbols app). Prefer `.symbolRenderingMode` and `.foregroundStyle` over tint hacks; multicolor symbols need `.renderingMode(.original)`; tab-bar symbols need the `.fill` variant.326. **Review haptic feedback.** `UIImpactFeedbackGenerator` and `UINotificationFeedbackGenerator` (or SwiftUI `.sensoryFeedback`) should mark meaningful state changes, not decorate animations. Call `.prepare()` before `.impactOccurred()` to avoid first-fire latency.337. **Audit dark mode support.** Replace hardcoded `UIColor(red:...)` and `Color(red:...)` with semantic colors (`Color(.systemBackground)`, `.label`, `.secondaryLabel`). Confirm asset-catalog images have dark variants.348. **Review the navigation paradigm.** Use `NavigationStack` (iOS 16 and later) instead of the deprecated `NavigationView`. Modal sheets are for self-contained tasks, not drill-down. The back-button label should reflect the parent title, not a generic "Back".359. **Check sheet and presentation style.** Confirm `.sheet`, `.fullScreenCover`, and `.presentationDetents` are appropriate; destructive actions in a sheet require explicit confirmation.3610. **Review accessibility annotations.** Confirm `.accessibilityLabel`, `.accessibilityHint`, and `.accessibilityValue` on custom controls; mark decorative images `.accessibilityHidden(true)`; verify `UIAccessibility.isReduceMotionEnabled` is respected for non-essential animation.3711. **Summarize findings** with HIG section references and severity.3839## Concrete checks4041Layout and type:42- Content does not underlap the notch, Dynamic Island, or home indicator unintentionally.43- All text uses a text style and scales with Dynamic Type.44- No truncation at the largest accessibility sizes.4546Touch and symbols:47- Every interactive element is at least 44x44 pt.48- SF Symbol names are valid for the deployment target with the correct rendering mode.49- Tab-bar symbols use the `.fill` variant.5051Color and motion:52- No hardcoded colors that break dark mode; asset catalog has dark variants.53- Haptics mark meaningful feedback and call `.prepare()` first.54- Non-essential animation respects Reduce Motion.5556Navigation and accessibility:57- `NavigationStack` is used on iOS 16 and later; modals are for tasks, not drill-down.58- The back-button label reflects the parent screen.59- Custom controls have VoiceOver labels and hints; decorative images are hidden.60- `Info.plist` privacy usage strings are present and accurate.6162## Commands6364```bash65# --- Dynamic Type ---66# hardcoded fonts that break Dynamic Type67rg -n 'Font.system\(size:|UIFont.systemFont\(ofSize:|\.font\(.system\(size:' .6869# --- dark mode ---70# hardcoded colors that break dark mode71rg -n 'UIColor\(red:|Color\(red:|UIColor\(white:|#[0-9a-fA-F]{6}' .7273# --- navigation ---74# deprecated NavigationView (should be NavigationStack on iOS 16+)75rg -n 'NavigationView\b' .7677# custom back buttons that may drop the parent title78rg -n 'navigationBarBackButtonHidden|\.backButtonTitle' .7980# --- SF Symbols / tap targets ---81# small icon-only buttons — verify 44pt hit area nearby82rg -n 'Image\(systemName:' . | head -408384# tab-bar symbols missing the fill variant85rg -n 'TabItem|tabItem' . | head8687# --- haptics ---88# haptic generators and whether prepare() is called89rg -n 'FeedbackGenerator|impactOccurred|sensoryFeedback|\.prepare\(\)' . | head9091# --- accessibility ---92# accessibility annotations present on custom controls?93rg -n 'accessibilityLabel|accessibilityHint|accessibilityHidden' . | head9495# Reduce Motion respected?96rg -n 'isReduceMotionEnabled|accessibilityReduceMotion' .9798# --- privacy ---99# Info.plist privacy usage strings100rg -n 'NSCameraUsageDescription|NSMicrophoneUsageDescription|NSLocationWhenInUseUsageDescription' .101102# --- sheets / presentation ---103# sheet, full-screen cover, and detents usage104rg -n '\.sheet|fullScreenCover|presentationDetents' . | head105106# --- alerts ---107# UIKit alerts presented from SwiftUI (sheet-stacking bug on iOS 17+)108rg -n 'UIAlertController|keyWindow' . | head109110# --- truncation at large sizes ---111# labels that may truncate under accessibility Dynamic Type sizes112rg -n 'lineLimit\(|truncationMode|fixedSize' . | head113```114115## Common issues & anti-patterns116117- **Ignoring safe area globally:** `.ignoresSafeArea()` on a scroll view hides content under the Dynamic Island on iPhone 14 Pro and later.118- **Hardcoded font sizes:** `Font.system(size: 14)` does not scale with Dynamic Type — use `.body`, `.caption`, `.footnote`, or `relativeTo:`.119- **Custom back button without a title:** stripping the back-button label breaks conventions and confuses VoiceOver users.120- **UIKit alert from SwiftUI:** presenting a `UIAlertController` via `UIApplication.shared.keyWindow` breaks sheet stacking on iOS 17 and later. Use the `.alert` modifier.121- **Missing `.symbolVariant`:** `Image(systemName: "heart")` in a tab bar without `.fill` violates tab-bar conventions.122- **Non-semantic colors in widgets:** WidgetKit ignores dynamic colors unless `Color(.widgetBackground)` and `.widgetAccentable()` are used.123- **Blocking the main thread during scroll:** synchronous image loading in `cellForRowAt` causes scroll jank — load asynchronously.124- **Decorative haptics:** firing impact feedback on every minor animation, which feels noisy and trains users to ignore it.125- **Sheet for navigation:** using a modal `.sheet` to push a detail screen that should be a `NavigationStack` destination, so the back gesture and title behavior feel wrong.126- **Fixed-size icon button:** a 20pt icon in a 24pt frame used as a tappable button, well under the 44pt minimum and hard to hit reliably.127- **No confirmation on destructive sheet:** a delete action inside a sheet that fires immediately on tap with no confirmation, risking accidental data loss.128- **Light-only asset:** an image asset with no dark-mode variant in the catalog, so a logo or illustration glows on a dark background.129- **Hardcoded accent color:** a brand color set as a literal `Color(red:...)` instead of an asset-catalog color set, so it ignores both dark mode and high-contrast accessibility settings.130- **VoiceOver order scrambled:** a custom layout where the visual order and the accessibility traversal order diverge, so VoiceOver reads controls in a confusing sequence.131132## Required output133134Return a structured report with:1351. **Framework and iOS version summary.**1362. **Findings table:** Screen/Component, HIG Section, Severity, Issue, Fix.1373. **VoiceOver and Dynamic Type pass/fail summary.**1384. **Top three must-fix items** before App Store submission.139140## Safety141142- Do not alter `Info.plist` privacy strings without listing every change explicitly for the user to review.143- Do not trigger Xcode builds or TestFlight uploads.144- Do not read or reproduce entitlement files or provisioning-profile details.145- Redact any signing identity or team-ID values surfaced during review.146- Treat accessibility and privacy gaps as submission blockers, not cosmetic notes.147148## Completion criteria149150Done means the framework and deployment target are identified; safe area, Dynamic Type, tap targets, SF Symbols, haptics, dark mode, navigation, and VoiceOver were each checked against HIG; every finding cites a HIG area and a concrete fix; and the top three submission blockers are called out with a VoiceOver and Dynamic Type pass/fail summary.