[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6PPW84MBZXK5M4EDP40QXYN":3,"skill-same:01M6PPW84MBZXK5M4EDP40QXYN":92},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":14,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":62,"ownerDescription":6,"updatedAt":63,"install":64,"resources":77},"01M6PPW84MBZXK5M4EDP40QXYN","ios-design-guidelines",null,"Apple Human Interface Guidelines for iPhone. Use when building, reviewing, or refactoring SwiftUI\u002FUIKit interfaces for iOS. Triggers on tasks involving iPhone UI, iOS components, accessibility, Dynamic Type, Dark Mode, or HIG compliance.","headmktlucca-ui\u002Fneuroads-lp","headmktlucca-ui","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F247379838?v=4","01M6PDVAV6M1W96D1SR1PDF8CS","NeuroAds","01M6PDVAZ4P281GNDKD45CYTF5",0,"MIT","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fios\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"guidelines","ios","ios design","ios design guidelines","design","platform","dark","for","mode","interface","dark mode","type","triggers","human","use","uikit","swiftui","refactoring","ui","components","2026-11-10 10:12:30",false,"---\nname: ios-design-guidelines\ndescription: Apple Human Interface Guidelines for iPhone. Use when building, reviewing, or refactoring SwiftUI\u002FUIKit interfaces for iOS. Triggers on tasks involving iPhone UI, iOS components, accessibility, Dynamic Type, Dark Mode, or HIG compliance.\nlicense: MIT\nmetadata:\n  author: platform-design-skills\n  version: \"1.0.0\"\n---\n\n# iOS Design Guidelines for iPhone\n\nComprehensive rules derived from Apple's Human Interface Guidelines. Apply these when building, reviewing, or refactoring any iPhone app interface.\n\n---\n\n## 1. Layout & Safe Areas\n**Impact:** CRITICAL\n\n### Rule 1.1: Minimum 44pt Touch Targets\nAll interactive elements must have a minimum tap target of 44x44 points. This includes buttons, links, toggles, and custom controls.\n\n**Correct:**\n```swift\nButton(\"Save\") { save() }\n    .frame(minWidth: 44, minHeight: 44)\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F 20pt icon with no padding — too small to tap reliably\nButton(action: save) {\n    Image(systemName: \"checkmark\")\n        .font(.system(size: 20))\n}\n\u002F\u002F Missing .frame(minWidth: 44, minHeight: 44)\n```\n\n### Rule 1.2: Respect Safe Areas\nNever place interactive or essential content under the status bar, Dynamic Island, or home indicator. Use SwiftUI's automatic safe area handling or UIKit's `safeAreaLayoutGuide`.\n\n**Correct:**\n```swift\nstruct ContentView: View {\n    var body: some View {\n        VStack {\n            Text(\"Content\")\n        }\n        \u002F\u002F SwiftUI respects safe areas by default\n    }\n}\n```\n\n**Incorrect:**\n```swift\nstruct ContentView: View {\n    var body: some View {\n        VStack {\n            Text(\"Content\")\n        }\n        .ignoresSafeArea() \u002F\u002F Content will be clipped under notch\u002FDynamic Island\n    }\n}\n```\n\nUse `.ignoresSafeArea()` only for background fills, images, or decorative elements — never for text or interactive controls.\n\n### Rule 1.3: Primary Actions in the Thumb Zone\nPlace primary actions at the bottom of the screen where the user's thumb naturally rests. Secondary actions and navigation belong at the top.\n\n**Correct:**\n```swift\nVStack {\n    ScrollView { \u002F* content *\u002F }\n    Button(\"Continue\") { next() }\n        .buttonStyle(.borderedProminent)\n        .padding()\n}\n```\n\n**Incorrect:**\n```swift\nVStack {\n    Button(\"Continue\") { next() } \u002F\u002F Top of screen — hard to reach one-handed\n        .buttonStyle(.borderedProminent)\n        .padding()\n    ScrollView { \u002F* content *\u002F }\n}\n```\n\n### Rule 1.4: Support All iPhone Screen Sizes\nDesign for iPhone SE (375pt wide) through iPhone Pro Max (430pt wide). Use flexible layouts, avoid hardcoded widths.\n\n**Correct:**\n```swift\nHStack(spacing: 12) {\n    ForEach(items) { item in\n        CardView(item: item)\n            .frame(maxWidth: .infinity) \u002F\u002F Adapts to screen width\n    }\n}\n```\n\n**Incorrect:**\n```swift\nHStack(spacing: 12) {\n    ForEach(items) { item in\n        CardView(item: item)\n            .frame(width: 180) \u002F\u002F Breaks on SE, wastes space on Pro Max\n    }\n}\n```\n\n### Rule 1.5: 8pt Grid Alignment\nAlign spacing, padding, and element sizes to multiples of 8 points (8, 16, 24, 32, 40, 48). Use 4pt for fine adjustments.\n\n### Rule 1.6: Landscape Support\nSupport landscape orientation unless the app is task-specific (e.g., camera). Use `ViewThatFits` or `GeometryReader` for adaptive layouts.\n\n---\n\n## 2. Navigation\n**Impact:** CRITICAL\n\n### Rule 2.1: Tab Bar for Top-Level Sections\nUse a tab bar at the bottom of the screen for 3 to 5 top-level sections. Each tab should represent a distinct category of content or functionality.\n\n**Correct:**\n```swift\nTabView {\n    HomeView()\n        .tabItem {\n            Label(\"Home\", systemImage: \"house\")\n        }\n    SearchView()\n        .tabItem {\n            Label(\"Search\", systemImage: \"magnifyingglass\")\n        }\n    ProfileView()\n        .tabItem {\n            Label(\"Profile\", systemImage: \"person\")\n        }\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Hamburger menu hidden behind three lines — discoverability is near zero\nNavigationView {\n    Button(action: { showMenu.toggle() }) {\n        Image(systemName: \"line.horizontal.3\")\n    }\n}\n```\n\n### Rule 2.2: Never Use Hamburger Menus\nHamburger (drawer) menus hide navigation, reduce discoverability, and violate iOS conventions. Use a tab bar instead. If you have more than 5 sections, consolidate or use a \"More\" tab.\n\n### Rule 2.3: Large Titles in Primary Views\nUse `.navigationBarTitleDisplayMode(.large)` for top-level views. Titles transition to inline (`.inline`) when the user scrolls.\n\n**Correct:**\n```swift\nNavigationStack {\n    List(items) { item in\n        ItemRow(item: item)\n    }\n    .navigationTitle(\"Messages\")\n    .navigationBarTitleDisplayMode(.large)\n}\n```\n\n### Rule 2.4: Never Override Back Swipe\nThe swipe-from-left-edge gesture for back navigation is a system-level expectation. Never attach custom gesture recognizers that interfere with it.\n\n**Incorrect:**\n```swift\n.gesture(\n    DragGesture()\n        .onChanged { \u002F* custom drawer *\u002F } \u002F\u002F Conflicts with system back swipe\n)\n```\n\n### Rule 2.5: Use NavigationStack for Hierarchical Content\nUse `NavigationStack` (not the deprecated `NavigationView`) for drill-down content. Use `NavigationPath` for programmatic navigation.\n\n**Correct:**\n```swift\nNavigationStack(path: $path) {\n    List(items) { item in\n        NavigationLink(value: item) {\n            ItemRow(item: item)\n        }\n    }\n    .navigationDestination(for: Item.self) { item in\n        ItemDetail(item: item)\n    }\n}\n```\n\n### Rule 2.6: Preserve State Across Navigation\nWhen users navigate back and then forward, or switch tabs, restore the previous scroll position and input state. Use `@SceneStorage` or `@State` to persist view state.\n\n### Rule 2.7: Prefer Recognition Over Recall\nKeep current location, recent choices, and available destinations visible. Restore tab, scroll, filter, and selection state so users continue from recognition instead of reconstructing context from memory.\n\n---\n\n## 3. Typography & Dynamic Type\n**Impact:** HIGH\n\n### Rule 3.1: Use Built-in Text Styles\nAlways use semantic text styles rather than hardcoded sizes. These scale automatically with Dynamic Type.\n\n**Correct:**\n```swift\nVStack(alignment: .leading, spacing: 4) {\n    Text(\"Section Title\")\n        .font(.headline)\n    Text(\"Body content that explains the section.\")\n        .font(.body)\n    Text(\"Last updated 2 hours ago\")\n        .font(.caption)\n        .foregroundStyle(.secondary)\n}\n```\n\n**Incorrect:**\n```swift\nVStack(alignment: .leading, spacing: 4) {\n    Text(\"Section Title\")\n        .font(.system(size: 17, weight: .semibold)) \u002F\u002F Won't scale with Dynamic Type\n    Text(\"Body content\")\n        .font(.system(size: 15)) \u002F\u002F Won't scale with Dynamic Type\n}\n```\n\n### Rule 3.2: Support Dynamic Type Including Accessibility Sizes\nDynamic Type can scale text up to approximately 200% at the largest accessibility sizes. Layouts must reflow — never truncate or clip essential text.\n\n**Correct:**\n```swift\nHStack {\n    Image(systemName: \"star\")\n    Text(\"Favorites\")\n        .font(.body)\n}\n\u002F\u002F At accessibility sizes, consider using ViewThatFits or\n\u002F\u002F AnyLayout to switch from HStack to VStack\n```\n\nUse `@Environment(\\.dynamicTypeSize)` to detect size category and adapt layouts:\n\n```swift\n@Environment(\\.dynamicTypeSize) var dynamicTypeSize\n\nvar body: some View {\n    if dynamicTypeSize.isAccessibilitySize {\n        VStack { content }\n    } else {\n        HStack { content }\n    }\n}\n```\n\n### Rule 3.3: Custom Fonts Must Scale with Dynamic Type\nIf you use a custom typeface, scale it so it responds to Dynamic Type. The API differs by framework.\n\n**Correct (SwiftUI):**\n```swift\nextension Font {\n    static func scaledCustom(size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font {\n        .custom(\"CustomFont-Regular\", size: size, relativeTo: textStyle)\n    }\n}\n\n\u002F\u002F Usage\nText(\"Hello\")\n    .font(.scaledCustom(size: 17, relativeTo: .body))\n```\n\n**Correct (UIKit):**\n```swift\nlet metrics = UIFontMetrics(forTextStyle: .body)\nlet customFont = UIFont(name: \"CustomFont-Regular\", size: 17)!\nlabel.font = metrics.scaledFont(for: customFont)\nlabel.adjustsFontForContentSizeCategory = true\n```\n\n### Rule 3.4: SF Pro as System Font\nUse the system font (SF Pro) unless brand requirements dictate otherwise. SF Pro is optimized for legibility on Apple displays.\n\n### Rule 3.5: Minimum 11pt Text\nNever display text smaller than 11pt. Prefer 17pt for body text. Use the `caption2` style (11pt) as the absolute minimum.\n\n### Rule 3.6: Hierarchy Through Weight and Size\nEstablish visual hierarchy through font weight and size. Do not rely solely on color to differentiate text levels.\n\n---\n\n## 4. Color & Dark Mode\n**Impact:** HIGH\n\n### Rule 4.1: Use Semantic System Colors\nUse system-provided semantic colors that automatically adapt to light and dark modes.\n\n**Correct:**\n```swift\nText(\"Primary text\")\n    .foregroundStyle(.primary) \u002F\u002F Adapts to light\u002Fdark\n\nText(\"Secondary info\")\n    .foregroundStyle(.secondary)\n\nVStack { }\n    .background(Color(.systemBackground)) \u002F\u002F White in light, black in dark\n```\n\n**Incorrect:**\n```swift\nText(\"Primary text\")\n    .foregroundColor(.black) \u002F\u002F Invisible on dark backgrounds\n\nVStack { }\n    .background(.white) \u002F\u002F Blinding in Dark Mode\n```\n\n### Rule 4.2: Provide Light and Dark Variants for Custom Colors\nDefine custom colors in the asset catalog with both Any Appearance and Dark Appearance variants.\n\n```swift\n\u002F\u002F In Assets.xcassets, define \"BrandBlue\" with:\n\u002F\u002F Any Appearance: #0066CC\n\u002F\u002F Dark Appearance: #4DA3FF\n\nText(\"Brand text\")\n    .foregroundStyle(Color(\"BrandBlue\")) \u002F\u002F Automatically switches\n```\n\n### Rule 4.3: Never Rely on Color Alone\nAlways pair color with text, icons, or shapes to convey meaning. Approximately 8% of men have some form of color vision deficiency.\n\n**Correct:**\n```swift\nHStack {\n    Image(systemName: \"exclamationmark.triangle.fill\")\n        .foregroundStyle(.red)\n    Text(\"Error: Invalid email address\")\n        .foregroundStyle(.red)\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Only color indicates the error — invisible to colorblind users\nTextField(\"Email\", text: $email)\n    .border(isValid ? .green : .red)\n```\n\n### Rule 4.4: 4.5:1 Contrast Ratio Minimum\nAll text must meet WCAG AA contrast ratios: 4.5:1 for normal text, 3:1 for large text (18pt+ or 14pt+ bold).\n\n### Rule 4.5: Support Display P3 Wide Gamut\nUse Display P3 color space for vibrant, accurate colors on modern iPhones. Define colors in the asset catalog with the Display P3 gamut.\n\n### Rule 4.6: Background Hierarchy\nUse the three-level background hierarchy for depth:\n- `systemBackground` — primary surface\n- `secondarySystemBackground` — grouped content, cards\n- `tertiarySystemBackground` — elements within grouped content\n\n### Rule 4.7: One Accent Color for Interactive Elements\nChoose a single tint\u002Faccent color for all interactive elements (buttons, links, toggles). This creates a consistent, learnable visual language.\n\n```swift\n@main\nstruct MyApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .tint(.indigo) \u002F\u002F All interactive elements use indigo\n        }\n    }\n}\n```\n\n---\n\n## 5. Accessibility\n**Impact:** CRITICAL\n\n### Rule 5.1: VoiceOver Labels on All Interactive Elements\nEvery button, control, and interactive element must have a meaningful accessibility label.\n\n**Correct:**\n```swift\nButton(action: addToCart) {\n    Image(systemName: \"cart.badge.plus\")\n}\n.accessibilityLabel(\"Add to cart\")\n```\n\n**Incorrect:**\n```swift\nButton(action: addToCart) {\n    Image(systemName: \"cart.badge.plus\")\n}\n\u002F\u002F VoiceOver reads \"cart.badge.plus\" — meaningless to users\n```\n\n### Rule 5.2: Logical VoiceOver Navigation Order\nEnsure VoiceOver reads elements in a logical order. Use `.accessibilitySortPriority()` to adjust when the visual layout doesn't match the reading order.\n\n```swift\nVStack {\n    Text(\"Price: $29.99\")\n        .accessibilitySortPriority(1) \u002F\u002F Read second (lower number = lower priority)\n    Text(\"Product Name\")\n        .accessibilitySortPriority(2) \u002F\u002F Read first (higher number = higher priority)\n}\n```\n\n### Rule 5.3: Support Bold Text\nWhen the user enables Bold Text in Settings, custom-rendered text must adapt. SwiftUI text styles handle this automatically. For SwiftUI custom rendering, use `@Environment(\\.legibilityWeight)` to apply heavier weights. UIKit code must check `UIAccessibility.isBoldTextEnabled` and re-query on `UIAccessibility.boldTextStatusDidChangeNotification`.\n\n**Correct:**\n```swift\n\u002F\u002F SwiftUI — standard text styles adapt automatically\nText(\"Section Header\")\n    .font(.headline)\n\n\u002F\u002F SwiftUI — custom rendering respects legibilityWeight\n@Environment(\\.legibilityWeight) var legibilityWeight\n\nvar body: some View {\n    Text(\"Custom Label\")\n        .fontWeight(legibilityWeight == .bold ? .bold : .regular)\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Hardcoded weight ignores Bold Text preference\nlabel.font = UIFont.systemFont(ofSize: 17, weight: .regular)\n\u002F\u002F Missing: re-query font when UIAccessibility.boldTextStatusDidChangeNotification fires\n```\n\n### Rule 5.4: Support Reduce Motion\nDisable decorative animations and parallax when Reduce Motion is enabled. Use `@Environment(\\.accessibilityReduceMotion)`.\n\n**Correct:**\n```swift\n@Environment(\\.accessibilityReduceMotion) var reduceMotion\n\nvar body: some View {\n    CardView()\n        .animation(reduceMotion ? nil : .spring(), value: isExpanded)\n}\n```\n\n### Rule 5.5: Support Increase Contrast\nWhen the user enables Increase Contrast, ensure custom colors have higher-contrast variants. Use `@Environment(\\.colorSchemeContrast)` to detect.\n\n### Rule 5.6: Don't Convey Info Only by Color, Shape, or Position\nInformation must be available through multiple channels. Pair visual indicators with text or accessibility descriptions.\n\n### Rule 5.7: Alternative Interactions for All Gestures\nEvery custom gesture must have an equivalent tap-based or menu-based alternative for users who cannot perform complex gestures.\n\n### Rule 5.8: Support Switch Control and Full Keyboard Access\nEnsure all interactions work with Switch Control (external switches) and Full Keyboard Access (Bluetooth keyboards). Test navigation order and focus behavior.\n\n---\n\n## 6. Gestures & Input\n**Impact:** HIGH\n\n### Rule 6.1: Use Standard Gestures\nUse the standard iOS gesture vocabulary: tap, long press, swipe, pinch, rotate. Users already understand these.\n\n| Gesture | Standard Use |\n|---------|-------------|\n| Tap | Primary action, selection |\n| Long press | Context menu, preview |\n| Swipe horizontal | Delete, archive, navigate back |\n| Swipe vertical | Scroll, dismiss sheet |\n| Pinch | Zoom in\u002Fout |\n| Two-finger rotate | Rotate content |\n\n### Rule 6.2: Never Override System Gestures\nThese gestures are reserved by the system and must not be intercepted:\n- Swipe from left edge (back navigation)\n- Swipe down from top-left (Notification Center)\n- Swipe down from top-right (Control Center)\n- Swipe up from bottom (home \u002F app switcher)\n\n### Rule 6.3: Custom Gestures Must Be Discoverable\nIf you add a custom gesture, provide visual hints (e.g., a grabber handle) and ensure the action is also available through a visible button or menu item.\n\n### Rule 6.4: Support All Input Methods\nDesign for touch first, but also support:\n- Hardware keyboards (iPad keyboard accessories, Bluetooth keyboards)\n- Assistive devices (Switch Control, head tracking)\n- Pointer input (assistive touch)\n\n---\n\n## 7. Components\n**Impact:** HIGH\n\n### Rule 7.1: Button Styles\nUse the built-in button styles appropriately:\n- `.borderedProminent` — primary call-to-action\n- `.bordered` — secondary actions\n- `.borderless` — tertiary or inline actions\n- `.destructive` role — red tint for delete\u002Fremove\n\n**Correct:**\n```swift\nVStack(spacing: 16) {\n    Button(\"Purchase\") { buy() }\n        .buttonStyle(.borderedProminent)\n\n    Button(\"Add to Wishlist\") { wishlist() }\n        .buttonStyle(.bordered)\n\n    Button(\"Delete\", role: .destructive) { delete() }\n}\n```\n\n### Rule 7.2: Alerts — Critical Info Only\nUse alerts sparingly for critical information that requires a decision. Prefer 2 buttons; maximum 3. The destructive option should use `.destructive` role.\n\n**Correct:**\n```swift\n.alert(\"Delete Photo?\", isPresented: $showAlert) {\n    Button(\"Delete\", role: .destructive) { deletePhoto() }\n    Button(\"Cancel\", role: .cancel) { }\n} message: {\n    Text(\"This photo will be permanently removed.\")\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Alert for non-critical info — should be a banner or toast\n.alert(\"Tip\", isPresented: $showTip) {\n    Button(\"OK\") { }\n} message: {\n    Text(\"Swipe left to delete items.\")\n}\n```\n\n### Rule 7.3: Sheets for Scoped Tasks\nPresent sheets for self-contained tasks. Always provide a way to dismiss (close button or swipe down). Use `.presentationDetents()` for half-height sheets.\n\n```swift\n.sheet(isPresented: $showCompose) {\n    NavigationStack {\n        ComposeView()\n            .navigationTitle(\"New Message\")\n            .toolbar {\n                ToolbarItem(placement: .cancellationAction) {\n                    Button(\"Cancel\") { showCompose = false }\n                }\n                ToolbarItem(placement: .confirmationAction) {\n                    Button(\"Send\") { send() }\n                }\n            }\n    }\n    .presentationDetents([.medium, .large])\n}\n```\n\n### Rule 7.4: Lists — Inset Grouped Default\nUse the `.insetGrouped` list style as the default. Support swipe actions for common operations. Minimum row height is 44pt.\n\n**Correct:**\n```swift\nList {\n    Section(\"Recent\") {\n        ForEach(recentItems) { item in\n            ItemRow(item: item)\n                .swipeActions(edge: .trailing) {\n                    Button(role: .destructive) { delete(item) } label: {\n                        Label(\"Delete\", systemImage: \"trash\")\n                    }\n                    Button { archive(item) } label: {\n                        Label(\"Archive\", systemImage: \"archivebox\")\n                    }\n                    .tint(.blue)\n                }\n        }\n    }\n}\n.listStyle(.insetGrouped)\n```\n\n### Rule 7.5: Tab Bar Behavior\n- Use SF Symbols for tab icons — filled variant for the selected tab, outline for unselected\n- Never hide the tab bar when navigating deeper within a tab\n- Badge important counts with `.badge()`\n\n```swift\nTabView {\n    MessagesView()\n        .tabItem {\n            Label(\"Messages\", systemImage: \"message\")\n        }\n        .badge(unreadCount)\n}\n```\n\n### Rule 7.6: Search\nPlace search using `.searchable()`. Provide search suggestions and support recent searches.\n\n```swift\nNavigationStack {\n    List(filteredItems) { item in\n        ItemRow(item: item)\n    }\n    .searchable(text: $searchText, prompt: \"Search items\")\n    .searchSuggestions {\n        ForEach(suggestions) { suggestion in\n            Text(suggestion.title)\n                .searchCompletion(suggestion.title)\n        }\n    }\n}\n```\n\n### Rule 7.7: Context Menus\nUse context menus (long press) for secondary actions. Never use a context menu as the only way to access an action.\n\n```swift\nPhotoView(photo: photo)\n    .contextMenu {\n        Button { share(photo) } label: {\n            Label(\"Share\", systemImage: \"square.and.arrow.up\")\n        }\n        Button { favorite(photo) } label: {\n            Label(\"Favorite\", systemImage: \"heart\")\n        }\n        Button(role: .destructive) { delete(photo) } label: {\n            Label(\"Delete\", systemImage: \"trash\")\n        }\n    }\n```\n\n### Rule 7.8: Progress Indicators\n- Determinate (`ProgressView(value:total:)`) for operations with known duration\n- Indeterminate (`ProgressView()`) for unknown duration\n- Never block the entire screen with a spinner\n\n### Rule 7.9: SF Symbols — Rendering Modes\nUse the appropriate rendering mode for each symbol. Monochrome is the default; hierarchical, palette, and multicolor provide richer expression where appropriate. Always prefer the symbol rendering mode that best communicates meaning — do not default to monochrome when multicolor conveys critical state.\n\n**Correct:**\n```swift\n\u002F\u002F Hierarchical: single color with automatic opacity layers\nImage(systemName: \"person.crop.circle.fill\")\n    .symbolRenderingMode(.hierarchical)\n    .foregroundStyle(.blue)\n\n\u002F\u002F Multicolor: system-defined color per layer (e.g., battery, weather)\nImage(systemName: \"battery.100percent.bolt\")\n    .symbolRenderingMode(.multicolor)\n\n\u002F\u002F Palette: explicit per-layer colors\nImage(systemName: \"folder.badge.plus\")\n    .symbolRenderingMode(.palette)\n    .foregroundStyle(.white, .blue)\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Monochrome on a symbol that has meaningful multicolor layers\nImage(systemName: \"battery.100percent.bolt\")\n    .foregroundColor(.gray) \u002F\u002F loses the contextual color meaning\n```\n\n### Rule 7.10: SF Symbols — Weight and Scale\nMatch the symbol weight to adjacent text weight. Use scale variants (`.small`, `.medium`, `.large`) rather than resizing. The symbol weight should never appear heavier than adjacent text.\n\n**Correct:**\n```swift\nLabel(\"Download\", systemImage: \"arrow.down.circle.fill\")\n    .font(.body.weight(.semibold))\n    \u002F\u002F Symbol inherits .semibold weight automatically via Label\n```\n\n**Incorrect:**\n```swift\nHStack {\n    Image(systemName: \"arrow.down.circle.fill\")\n        .font(.system(size: 32)) \u002F\u002F explicit size ignores type scale\n    Text(\"Download\")\n        .font(.body)\n}\n```\n\n### Rule 7.11: SF Symbols — Animations (iOS 17+)\nUse `symbolEffect` for symbol state transitions. Prefer discrete effects (`.bounce`, `.pulse`) for actions and indefinite effects (`.variableColor`) for ongoing state. Do not use manual cross-fade between symbol names when `contentTransition(.symbolEffect)` is available.\n\n**Correct:**\n```swift\nImage(systemName: isLoading ? \"arrow.2.circlepath\" : \"checkmark.circle\")\n    .contentTransition(.symbolEffect(.replace))\n    .symbolEffect(.pulse, isActive: isLoading)\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Manual opacity cross-fade between symbol names\nif isLoading {\n    Image(systemName: \"arrow.2.circlepath\")\n} else {\n    Image(systemName: \"checkmark.circle\")\n}\n```\n\n---\n\n## 8. Patterns\n**Impact:** MEDIUM\n\n### Rule 8.1: Onboarding — Max 3 Pages, Skippable\nKeep onboarding to 3 or fewer pages. Always provide a skip option. Defer sign-in until the user needs authenticated features.\n\n```swift\nTabView {\n    OnboardingPage(\n        image: \"wand.and.stars\",\n        title: \"Smart Suggestions\",\n        subtitle: \"Get personalized recommendations based on your preferences.\"\n    )\n    OnboardingPage(\n        image: \"bell.badge\",\n        title: \"Stay Updated\",\n        subtitle: \"Receive notifications for things that matter to you.\"\n    )\n    OnboardingPage(\n        image: \"checkmark.shield\",\n        title: \"Private & Secure\",\n        subtitle: \"Your data stays on your device.\"\n    )\n}\n.tabViewStyle(.page)\n.overlay(alignment: .topTrailing) {\n    Button(\"Skip\") { completeOnboarding() }\n        .padding()\n}\n```\n\n### Rule 8.2: Loading — Skeleton Views, No Blocking Spinners\nUse skeleton\u002Fplaceholder views that match the layout of the content being loaded. Never show a full-screen blocking spinner.\n\n**Correct:**\n```swift\nif isLoading {\n    ForEach(0..\u003C5) { _ in\n        SkeletonRow() \u002F\u002F Placeholder matching final row layout\n            .redacted(reason: .placeholder)\n    }\n} else {\n    ForEach(items) { item in\n        ItemRow(item: item)\n    }\n}\n```\n\n**Incorrect:**\n```swift\nif isLoading {\n    ProgressView(\"Loading...\") \u002F\u002F Blocks the entire view\n} else {\n    List(items) { item in ItemRow(item: item) }\n}\n```\n\n### Rule 8.3: Launch Screen — Match First Screen\nThe launch storyboard must visually match the initial screen of the app. No splash logos, no branding screens. This creates the perception of instant launch.\n\n### Rule 8.4: Modality — Use Sparingly\nPresent modal views only when the user must complete or abandon a focused task. Always provide a clear dismiss action. Never stack modals on top of modals.\n\n### Rule 8.5: Notifications — High Value Only\nOnly send notifications for content the user genuinely cares about. Support actionable notifications. Categorize notifications so users can control them granularly.\n\n### Rule 8.6: Settings Placement\n- **Frequent settings:** In-app settings screen accessible from a profile or gear icon\n- **Privacy\u002Fpermission settings:** Defer to the system Settings app via URL scheme\n- Never duplicate system-level controls in-app\n\n### Rule 8.7: Feedback — Visual + Haptic\nProvide immediate feedback for every user action:\n- Visual state change (button highlight, animation)\n- Haptic feedback for significant actions using `UIImpactFeedbackGenerator`, `UINotificationFeedbackGenerator`, or `UISelectionFeedbackGenerator`\n\n```swift\nButton(\"Complete\") {\n    let generator = UINotificationFeedbackGenerator()\n    generator.notificationOccurred(.success)\n    completeTask()\n}\n```\n\n### Rule 8.8: Show Waiting States Immediately\nIf an action cannot complete immediately, acknowledge the tap at once, then show inline progress, skeletons, or partial results. Never leave the interface visually unchanged while work continues.\n\n---\n\n## 9. Privacy & Permissions\n**Impact:** HIGH\n\n### Rule 9.1: Request Permissions in Context\nRequest a permission at the moment the user takes an action that needs it — never at app launch.\n\n**Correct:**\n```swift\nButton(\"Take Photo\") {\n    \u002F\u002F Request camera permission only when the user taps this button\n    AVCaptureDevice.requestAccess(for: .video) { granted in\n        if granted { showCamera = true }\n    }\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F In AppDelegate.didFinishLaunching — too early, no context\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions ...) {\n    AVCaptureDevice.requestAccess(for: .video) { _ in }\n    CLLocationManager().requestWhenInUseAuthorization()\n    UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { _, _ in }\n}\n```\n\n### Rule 9.2: Explain Before System Prompt\nShow a custom explanation screen before triggering the system permission dialog. The system dialog only appears once — if the user denies, the app must direct them to Settings.\n\n```swift\nstruct LocationExplanation: View {\n    var body: some View {\n        VStack(spacing: 16) {\n            Image(systemName: \"location.fill\")\n                .font(.largeTitle)\n            Text(\"Find Nearby Stores\")\n                .font(.headline)\n            Text(\"We use your location to show stores within walking distance. Your location is never shared or stored.\")\n                .font(.body)\n                .multilineTextAlignment(.center)\n            Button(\"Enable Location\") {\n                locationManager.requestWhenInUseAuthorization()\n            }\n            .buttonStyle(.borderedProminent)\n            Button(\"Not Now\") { dismiss() }\n                .foregroundStyle(.secondary)\n        }\n        .padding()\n    }\n}\n```\n\n### Rule 9.3: Support Sign in with Apple\nIf the app offers any third-party sign-in (Google, Facebook), it must also offer Sign in with Apple. Present it as the first option.\n\n### Rule 9.4: Don't Require Accounts Unless Necessary\nLet users explore the app before requiring sign-in. Gate only features that genuinely need authentication (purchases, sync, social features).\n\n### Rule 9.5: App Tracking Transparency\nIf you track users across apps or websites, display the ATT prompt. Respect denial — do not degrade the experience for users who opt out.\n\n### Rule 9.6: Location Button for One-Time Access\nUse `LocationButton` for actions that need location once without requesting ongoing permission.\n\n```swift\nimport CoreLocationUI\n\nLocationButton(.currentLocation) {\n    fetchNearbyStores()\n}\n.labelStyle(.titleAndIcon)\n```\n\n---\n\n## 10. System Integration\n**Impact:** MEDIUM\n\n### Rule 10.1: Widgets for Glanceable Data\nProvide widgets using WidgetKit for information users check frequently. Show the most useful snapshot. Since iOS 17, widgets support interactive controls: use `Button` and `Toggle` backed by App Intents for actions users perform directly from the widget without opening the app.\n\n```swift\n\u002F\u002F iOS 17+ interactive widget with a Button\nstruct TimerWidgetView: View {\n    let entry: TimerEntry\n\n    var body: some View {\n        VStack {\n            Text(entry.remaining, style: .timer)\n                .font(.title2.bold())\n            Button(intent: ToggleTimerIntent()) {\n                Label(entry.isRunning ? \"Pause\" : \"Start\",\n                      systemImage: entry.isRunning ? \"pause.fill\" : \"play.fill\")\n            }\n            .buttonStyle(.borderedProminent)\n        }\n    }\n}\n```\n\n### Rule 10.2: App Shortcuts for Key Actions\nDefine App Shortcuts so users can trigger key actions from Siri, Spotlight, and the Shortcuts app.\n\n```swift\nstruct MyAppShortcuts: AppShortcutsProvider {\n    static var appShortcuts: [AppShortcut] {\n        AppShortcut(\n            intent: StartWorkoutIntent(),\n            phrases: [\"Start a workout in \\(.applicationName)\"],\n            shortTitle: \"Start Workout\",\n            systemImageName: \"figure.run\"\n        )\n    }\n}\n```\n\n### Rule 10.3: Spotlight Indexing\nIndex app content with `CSSearchableItem` so users can find it from Spotlight search.\n\n### Rule 10.4: Share Sheet Integration\nSupport the system share sheet for content that users might want to send elsewhere. Implement `UIActivityItemSource` or use `ShareLink` in SwiftUI.\n\n```swift\nShareLink(item: article.url) {\n    Label(\"Share\", systemImage: \"square.and.arrow.up\")\n}\n```\n\n### Rule 10.5: Live Activities\nUse Live Activities and the Dynamic Island for real-time, time-bound events (delivery tracking, sports scores, workouts).\n\n### Rule 10.6: Handle Interruptions Gracefully\nSave state and pause gracefully when interrupted by:\n- Phone calls\n- Siri invocations\n- Notifications\n- App switcher\n- FaceTime SharePlay\n\nUse `scenePhase` to detect transitions:\n\n```swift\n@Environment(\\.scenePhase) var scenePhase\n\n.onChange(of: scenePhase) { _, newPhase in\n    switch newPhase {\n    case .active: resumeActivity()\n    case .inactive: pauseActivity()\n    case .background: saveState()\n    @unknown default: break\n    }\n}\n```\n\n---\n\n## Quick Reference\n\n| Need | Component | Notes |\n|------|-----------|-------|\n| Top-level sections (3-5) | `TabView` with `.tabItem` | Bottom tab bar, SF Symbols |\n| Hierarchical drill-down | `NavigationStack` | Large title on root, inline on children |\n| Self-contained task | `.sheet` | Swipe to dismiss, cancel\u002Fdone buttons |\n| Critical decision | `.alert` | 2 buttons preferred, max 3 |\n| Secondary actions | `.contextMenu` | Long press; must also be accessible elsewhere |\n| Scrolling content | `List` with `.insetGrouped` | 44pt min row, swipe actions |\n| Text input | `TextField` \u002F `TextEditor` | Label above, validation below |\n| Selection (few options) | `Picker` | Segmented for 2-5, wheel for many |\n| Selection (on\u002Foff) | `Toggle` | Aligned right in a list row |\n| Search | `.searchable` | Suggestions, recent searches |\n| Progress (known) | `ProgressView(value:total:)` | Show percentage or time remaining |\n| Progress (unknown) | `ProgressView()` | Inline, never full-screen blocking |\n| One-time location | `LocationButton` | No persistent permission needed |\n| Sharing content | `ShareLink` | System share sheet |\n| Haptic feedback | `UIImpactFeedbackGenerator` | `.light`, `.medium`, `.heavy` |\n| Destructive action | `Button(role: .destructive)` | Red tint, confirm via alert |\n\n---\n\n## Evaluation Checklist\n\nUse this checklist to audit an iPhone app for HIG compliance:\n\n### Layout & Safe Areas\n- [ ] All touch targets are at least 44x44pt\n- [ ] No content is clipped under status bar, Dynamic Island, or home indicator\n- [ ] Primary actions are in the bottom half of the screen (thumb zone)\n- [ ] Layout adapts from iPhone SE to Pro Max without breaking\n- [ ] Spacing aligns to the 8pt grid\n\n### Navigation\n- [ ] Tab bar is used for 3-5 top-level sections\n- [ ] No hamburger\u002Fdrawer menus\n- [ ] Primary views use large titles\n- [ ] Swipe-from-left-edge back navigation works throughout\n- [ ] State is preserved when switching tabs\n\n### Typography\n- [ ] All text uses built-in text styles or custom fonts scaled with Dynamic Type (`Font.custom(_:size:relativeTo:)` in SwiftUI or `UIFontMetrics` in UIKit)\n- [ ] Dynamic Type is supported up to accessibility sizes\n- [ ] Layouts reflow at large text sizes (no truncation of essential text)\n- [ ] Minimum text size is 11pt\n\n### Color & Dark Mode\n- [ ] App uses semantic system colors or provides light\u002Fdark asset variants\n- [ ] Dark Mode looks intentional (not just inverted)\n- [ ] No information conveyed by color alone\n- [ ] Text contrast meets 4.5:1 (normal) or 3:1 (large)\n- [ ] Single accent color for interactive elements\n\n### Accessibility\n- [ ] VoiceOver reads all screens logically with meaningful labels\n- [ ] Bold Text preference is respected\n- [ ] Reduce Motion disables decorative animations\n- [ ] Increase Contrast variant exists for custom colors\n- [ ] All gestures have alternative access paths\n\n### Components\n- [ ] Alerts are used only for critical decisions\n- [ ] Sheets have a dismiss path (button and\u002For swipe)\n- [ ] List rows are at least 44pt tall\n- [ ] Tab bar is never hidden during navigation\n- [ ] Destructive buttons use the `.destructive` role\n\n### Privacy\n- [ ] Permissions are requested in context, not at launch\n- [ ] Custom explanation shown before each system permission dialog\n- [ ] Sign in with Apple offered alongside other providers\n- [ ] App is usable without an account for basic features\n- [ ] ATT prompt is shown if tracking, and denial is respected\n\n### System Integration\n- [ ] Widgets show glanceable, up-to-date information\n- [ ] App content is indexed for Spotlight\n- [ ] Share Sheet is available for shareable content\n- [ ] App handles interruptions (calls, background, Siri) gracefully\n\n---\n\n## Anti-Patterns\n\nThese are common mistakes that violate the iOS Human Interface Guidelines. Never do these:\n\n1. **Hamburger menus** — Use a tab bar. Hamburger menus hide navigation and reduce feature discoverability by up to 50%.\n\n2. **Custom back buttons that break swipe-back** — If you replace the back button, ensure the swipe-from-left-edge gesture still works via `NavigationStack`.\n\n3. **Full-screen blocking spinners** — Use skeleton views or inline progress indicators. Blocking spinners make the app feel frozen.\n\n4. **Splash screens with logos** — The launch screen must mirror the first screen of the app. Branding delays feel artificial.\n\n5. **Requesting all permissions at launch** — Asking for camera, location, notifications, and contacts on first launch guarantees most will be denied.\n\n6. **Hardcoded font sizes** — Use text styles. Hardcoded sizes ignore Dynamic Type and accessibility preferences, breaking the app for millions of users.\n\n7. **Using only color to indicate state** — Red\u002Fgreen for valid\u002Finvalid excludes colorblind users. Always pair with icons or text.\n\n8. **Alerts for non-critical information** — Alerts interrupt flow and require dismissal. Use banners, toasts, or inline messages for tips and non-critical information.\n\n9. **Hiding the tab bar on push** — Tab bars should remain visible throughout navigation within a tab. Hiding them disorients users.\n\n10. **Ignoring safe areas** — Using `.ignoresSafeArea()` on content views causes text and buttons to disappear under the notch, Dynamic Island, or home indicator.\n\n11. **Non-dismissable modals** — Every modal must have a clear dismiss path (close button, cancel, swipe down). Trapping users in a modal is hostile.\n\n12. **Custom gestures without alternatives** — A three-finger swipe for undo is unusable for many people. Provide a visible button or menu item as well.\n\n13. **Tiny touch targets** — Buttons and links smaller than 44pt cause mis-taps, especially in lists and toolbars.\n\n14. **Stacked modals** — Presenting a sheet on top of a sheet on top of a sheet creates navigation confusion. Use navigation within a single modal instead.\n\n15. **Dark Mode as an afterthought** — Using hardcoded colors means the app is either broken in Dark Mode or light mode. Always use semantic colors.\n","# iOS Design Guidelines for iPhone\n\nComprehensive rules derived from Apple's Human Interface Guidelines. Apply these when building, reviewing, or refactoring any iPhone app interface.\n\n---\n\n## 1. Layout & Safe Areas\n**Impact:** CRITICAL\n\n### Rule 1.1: Minimum 44pt Touch Targets\nAll interactive elements must have a minimum tap target of 44x44 points. This includes buttons, links, toggles, and custom controls.\n\n**Correct:**\n```swift\nButton(\"Save\") { save() }\n    .frame(minWidth: 44, minHeight: 44)\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F 20pt icon with no padding — too small to tap reliably\nButton(action: save) {\n    Image(systemName: \"checkmark\")\n        .font(.system(size: 20))\n}\n\u002F\u002F Missing .frame(minWidth: 44, minHeight: 44)\n```\n\n### Rule 1.2: Respect Safe Areas\nNever place interactive or essential content under the status bar, Dynamic Island, or home indicator. Use SwiftUI's automatic safe area handling or UIKit's `safeAreaLayoutGuide`.\n\n**Correct:**\n```swift\nstruct ContentView: View {\n    var body: some View {\n        VStack {\n            Text(\"Content\")\n        }\n        \u002F\u002F SwiftUI respects safe areas by default\n    }\n}\n```\n\n**Incorrect:**\n```swift\nstruct ContentView: View {\n    var body: some View {\n        VStack {\n            Text(\"Content\")\n        }\n        .ignoresSafeArea() \u002F\u002F Content will be clipped under notch\u002FDynamic Island\n    }\n}\n```\n\nUse `.ignoresSafeArea()` only for background fills, images, or decorative elements — never for text or interactive controls.\n\n### Rule 1.3: Primary Actions in the Thumb Zone\nPlace primary actions at the bottom of the screen where the user's thumb naturally rests. Secondary actions and navigation belong at the top.\n\n**Correct:**\n```swift\nVStack {\n    ScrollView { \u002F* content *\u002F }\n    Button(\"Continue\") { next() }\n        .buttonStyle(.borderedProminent)\n        .padding()\n}\n```\n\n**Incorrect:**\n```swift\nVStack {\n    Button(\"Continue\") { next() } \u002F\u002F Top of screen — hard to reach one-handed\n        .buttonStyle(.borderedProminent)\n        .padding()\n    ScrollView { \u002F* content *\u002F }\n}\n```\n\n### Rule 1.4: Support All iPhone Screen Sizes\nDesign for iPhone SE (375pt wide) through iPhone Pro Max (430pt wide). Use flexible layouts, avoid hardcoded widths.\n\n**Correct:**\n```swift\nHStack(spacing: 12) {\n    ForEach(items) { item in\n        CardView(item: item)\n            .frame(maxWidth: .infinity) \u002F\u002F Adapts to screen width\n    }\n}\n```\n\n**Incorrect:**\n```swift\nHStack(spacing: 12) {\n    ForEach(items) { item in\n        CardView(item: item)\n            .frame(width: 180) \u002F\u002F Breaks on SE, wastes space on Pro Max\n    }\n}\n```\n\n### Rule 1.5: 8pt Grid Alignment\nAlign spacing, padding, and element sizes to multiples of 8 points (8, 16, 24, 32, 40, 48). Use 4pt for fine adjustments.\n\n### Rule 1.6: Landscape Support\nSupport landscape orientation unless the app is task-specific (e.g., camera). Use `ViewThatFits` or `GeometryReader` for adaptive layouts.\n\n---\n\n## 2. Navigation\n**Impact:** CRITICAL\n\n### Rule 2.1: Tab Bar for Top-Level Sections\nUse a tab bar at the bottom of the screen for 3 to 5 top-level sections. Each tab should represent a distinct category of content or functionality.\n\n**Correct:**\n```swift\nTabView {\n    HomeView()\n        .tabItem {\n            Label(\"Home\", systemImage: \"house\")\n        }\n    SearchView()\n        .tabItem {\n            Label(\"Search\", systemImage: \"magnifyingglass\")\n        }\n    ProfileView()\n        .tabItem {\n            Label(\"Profile\", systemImage: \"person\")\n        }\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Hamburger menu hidden behind three lines — discoverability is near zero\nNavigationView {\n    Button(action: { showMenu.toggle() }) {\n        Image(systemName: \"line.horizontal.3\")\n    }\n}\n```\n\n### Rule 2.2: Never Use Hamburger Menus\nHamburger (drawer) menus hide navigation, reduce discoverability, and violate iOS conventions. Use a tab bar instead. If you have more than 5 sections, consolidate or use a \"More\" tab.\n\n### Rule 2.3: Large Titles in Primary Views\nUse `.navigationBarTitleDisplayMode(.large)` for top-level views. Titles transition to inline (`.inline`) when the user scrolls.\n\n**Correct:**\n```swift\nNavigationStack {\n    List(items) { item in\n        ItemRow(item: item)\n    }\n    .navigationTitle(\"Messages\")\n    .navigationBarTitleDisplayMode(.large)\n}\n```\n\n### Rule 2.4: Never Override Back Swipe\nThe swipe-from-left-edge gesture for back navigation is a system-level expectation. Never attach custom gesture recognizers that interfere with it.\n\n**Incorrect:**\n```swift\n.gesture(\n    DragGesture()\n        .onChanged { \u002F* custom drawer *\u002F } \u002F\u002F Conflicts with system back swipe\n)\n```\n\n### Rule 2.5: Use NavigationStack for Hierarchical Content\nUse `NavigationStack` (not the deprecated `NavigationView`) for drill-down content. Use `NavigationPath` for programmatic navigation.\n\n**Correct:**\n```swift\nNavigationStack(path: $path) {\n    List(items) { item in\n        NavigationLink(value: item) {\n            ItemRow(item: item)\n        }\n    }\n    .navigationDestination(for: Item.self) { item in\n        ItemDetail(item: item)\n    }\n}\n```\n\n### Rule 2.6: Preserve State Across Navigation\nWhen users navigate back and then forward, or switch tabs, restore the previous scroll position and input state. Use `@SceneStorage` or `@State` to persist view state.\n\n### Rule 2.7: Prefer Recognition Over Recall\nKeep current location, recent choices, and available destinations visible. Restore tab, scroll, filter, and selection state so users continue from recognition instead of reconstructing context from memory.\n\n---\n\n## 3. Typography & Dynamic Type\n**Impact:** HIGH\n\n### Rule 3.1: Use Built-in Text Styles\nAlways use semantic text styles rather than hardcoded sizes. These scale automatically with Dynamic Type.\n\n**Correct:**\n```swift\nVStack(alignment: .leading, spacing: 4) {\n    Text(\"Section Title\")\n        .font(.headline)\n    Text(\"Body content that explains the section.\")\n        .font(.body)\n    Text(\"Last updated 2 hours ago\")\n        .font(.caption)\n        .foregroundStyle(.secondary)\n}\n```\n\n**Incorrect:**\n```swift\nVStack(alignment: .leading, spacing: 4) {\n    Text(\"Section Title\")\n        .font(.system(size: 17, weight: .semibold)) \u002F\u002F Won't scale with Dynamic Type\n    Text(\"Body content\")\n        .font(.system(size: 15)) \u002F\u002F Won't scale with Dynamic Type\n}\n```\n\n### Rule 3.2: Support Dynamic Type Including Accessibility Sizes\nDynamic Type can scale text up to approximately 200% at the largest accessibility sizes. Layouts must reflow — never truncate or clip essential text.\n\n**Correct:**\n```swift\nHStack {\n    Image(systemName: \"star\")\n    Text(\"Favorites\")\n        .font(.body)\n}\n\u002F\u002F At accessibility sizes, consider using ViewThatFits or\n\u002F\u002F AnyLayout to switch from HStack to VStack\n```\n\nUse `@Environment(\\.dynamicTypeSize)` to detect size category and adapt layouts:\n\n```swift\n@Environment(\\.dynamicTypeSize) var dynamicTypeSize\n\nvar body: some View {\n    if dynamicTypeSize.isAccessibilitySize {\n        VStack { content }\n    } else {\n        HStack { content }\n    }\n}\n```\n\n### Rule 3.3: Custom Fonts Must Scale with Dynamic Type\nIf you use a custom typeface, scale it so it responds to Dynamic Type. The API differs by framework.\n\n**Correct (SwiftUI):**\n```swift\nextension Font {\n    static func scaledCustom(size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font {\n        .custom(\"CustomFont-Regular\", size: size, relativeTo: textStyle)\n    }\n}\n\n\u002F\u002F Usage\nText(\"Hello\")\n    .font(.scaledCustom(size: 17, relativeTo: .body))\n```\n\n**Correct (UIKit):**\n```swift\nlet metrics = UIFontMetrics(forTextStyle: .body)\nlet customFont = UIFont(name: \"CustomFont-Regular\", size: 17)!\nlabel.font = metrics.scaledFont(for: customFont)\nlabel.adjustsFontForContentSizeCategory = true\n```\n\n### Rule 3.4: SF Pro as System Font\nUse the system font (SF Pro) unless brand requirements dictate otherwise. SF Pro is optimized for legibility on Apple displays.\n\n### Rule 3.5: Minimum 11pt Text\nNever display text smaller than 11pt. Prefer 17pt for body text. Use the `caption2` style (11pt) as the absolute minimum.\n\n### Rule 3.6: Hierarchy Through Weight and Size\nEstablish visual hierarchy through font weight and size. Do not rely solely on color to differentiate text levels.\n\n---\n\n## 4. Color & Dark Mode\n**Impact:** HIGH\n\n### Rule 4.1: Use Semantic System Colors\nUse system-provided semantic colors that automatically adapt to light and dark modes.\n\n**Correct:**\n```swift\nText(\"Primary text\")\n    .foregroundStyle(.primary) \u002F\u002F Adapts to light\u002Fdark\n\nText(\"Secondary info\")\n    .foregroundStyle(.secondary)\n\nVStack { }\n    .background(Color(.systemBackground)) \u002F\u002F White in light, black in dark\n```\n\n**Incorrect:**\n```swift\nText(\"Primary text\")\n    .foregroundColor(.black) \u002F\u002F Invisible on dark backgrounds\n\nVStack { }\n    .background(.white) \u002F\u002F Blinding in Dark Mode\n```\n\n### Rule 4.2: Provide Light and Dark Variants for Custom Colors\nDefine custom colors in the asset catalog with both Any Appearance and Dark Appearance variants.\n\n```swift\n\u002F\u002F In Assets.xcassets, define \"BrandBlue\" with:\n\u002F\u002F Any Appearance: #0066CC\n\u002F\u002F Dark Appearance: #4DA3FF\n\nText(\"Brand text\")\n    .foregroundStyle(Color(\"BrandBlue\")) \u002F\u002F Automatically switches\n```\n\n### Rule 4.3: Never Rely on Color Alone\nAlways pair color with text, icons, or shapes to convey meaning. Approximately 8% of men have some form of color vision deficiency.\n\n**Correct:**\n```swift\nHStack {\n    Image(systemName: \"exclamationmark.triangle.fill\")\n        .foregroundStyle(.red)\n    Text(\"Error: Invalid email address\")\n        .foregroundStyle(.red)\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Only color indicates the error — invisible to colorblind users\nTextField(\"Email\", text: $email)\n    .border(isValid ? .green : .red)\n```\n\n### Rule 4.4: 4.5:1 Contrast Ratio Minimum\nAll text must meet WCAG AA contrast ratios: 4.5:1 for normal text, 3:1 for large text (18pt+ or 14pt+ bold).\n\n### Rule 4.5: Support Display P3 Wide Gamut\nUse Display P3 color space for vibrant, accurate colors on modern iPhones. Define colors in the asset catalog with the Display P3 gamut.\n\n### Rule 4.6: Background Hierarchy\nUse the three-level background hierarchy for depth:\n- `systemBackground` — primary surface\n- `secondarySystemBackground` — grouped content, cards\n- `tertiarySystemBackground` — elements within grouped content\n\n### Rule 4.7: One Accent Color for Interactive Elements\nChoose a single tint\u002Faccent color for all interactive elements (buttons, links, toggles). This creates a consistent, learnable visual language.\n\n```swift\n@main\nstruct MyApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .tint(.indigo) \u002F\u002F All interactive elements use indigo\n        }\n    }\n}\n```\n\n---\n\n## 5. Accessibility\n**Impact:** CRITICAL\n\n### Rule 5.1: VoiceOver Labels on All Interactive Elements\nEvery button, control, and interactive element must have a meaningful accessibility label.\n\n**Correct:**\n```swift\nButton(action: addToCart) {\n    Image(systemName: \"cart.badge.plus\")\n}\n.accessibilityLabel(\"Add to cart\")\n```\n\n**Incorrect:**\n```swift\nButton(action: addToCart) {\n    Image(systemName: \"cart.badge.plus\")\n}\n\u002F\u002F VoiceOver reads \"cart.badge.plus\" — meaningless to users\n```\n\n### Rule 5.2: Logical VoiceOver Navigation Order\nEnsure VoiceOver reads elements in a logical order. Use `.accessibilitySortPriority()` to adjust when the visual layout doesn't match the reading order.\n\n```swift\nVStack {\n    Text(\"Price: $29.99\")\n        .accessibilitySortPriority(1) \u002F\u002F Read second (lower number = lower priority)\n    Text(\"Product Name\")\n        .accessibilitySortPriority(2) \u002F\u002F Read first (higher number = higher priority)\n}\n```\n\n### Rule 5.3: Support Bold Text\nWhen the user enables Bold Text in Settings, custom-rendered text must adapt. SwiftUI text styles handle this automatically. For SwiftUI custom rendering, use `@Environment(\\.legibilityWeight)` to apply heavier weights. UIKit code must check `UIAccessibility.isBoldTextEnabled` and re-query on `UIAccessibility.boldTextStatusDidChangeNotification`.\n\n**Correct:**\n```swift\n\u002F\u002F SwiftUI — standard text styles adapt automatically\nText(\"Section Header\")\n    .font(.headline)\n\n\u002F\u002F SwiftUI — custom rendering respects legibilityWeight\n@Environment(\\.legibilityWeight) var legibilityWeight\n\nvar body: some View {\n    Text(\"Custom Label\")\n        .fontWeight(legibilityWeight == .bold ? .bold : .regular)\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Hardcoded weight ignores Bold Text preference\nlabel.font = UIFont.systemFont(ofSize: 17, weight: .regular)\n\u002F\u002F Missing: re-query font when UIAccessibility.boldTextStatusDidChangeNotification fires\n```\n\n### Rule 5.4: Support Reduce Motion\nDisable decorative animations and parallax when Reduce Motion is enabled. Use `@Environment(\\.accessibilityReduceMotion)`.\n\n**Correct:**\n```swift\n@Environment(\\.accessibilityReduceMotion) var reduceMotion\n\nvar body: some View {\n    CardView()\n        .animation(reduceMotion ? nil : .spring(), value: isExpanded)\n}\n```\n\n### Rule 5.5: Support Increase Contrast\nWhen the user enables Increase Contrast, ensure custom colors have higher-contrast variants. Use `@Environment(\\.colorSchemeContrast)` to detect.\n\n### Rule 5.6: Don't Convey Info Only by Color, Shape, or Position\nInformation must be available through multiple channels. Pair visual indicators with text or accessibility descriptions.\n\n### Rule 5.7: Alternative Interactions for All Gestures\nEvery custom gesture must have an equivalent tap-based or menu-based alternative for users who cannot perform complex gestures.\n\n### Rule 5.8: Support Switch Control and Full Keyboard Access\nEnsure all interactions work with Switch Control (external switches) and Full Keyboard Access (Bluetooth keyboards). Test navigation order and focus behavior.\n\n---\n\n## 6. Gestures & Input\n**Impact:** HIGH\n\n### Rule 6.1: Use Standard Gestures\nUse the standard iOS gesture vocabulary: tap, long press, swipe, pinch, rotate. Users already understand these.\n\n| Gesture | Standard Use |\n|---------|-------------|\n| Tap | Primary action, selection |\n| Long press | Context menu, preview |\n| Swipe horizontal | Delete, archive, navigate back |\n| Swipe vertical | Scroll, dismiss sheet |\n| Pinch | Zoom in\u002Fout |\n| Two-finger rotate | Rotate content |\n\n### Rule 6.2: Never Override System Gestures\nThese gestures are reserved by the system and must not be intercepted:\n- Swipe from left edge (back navigation)\n- Swipe down from top-left (Notification Center)\n- Swipe down from top-right (Control Center)\n- Swipe up from bottom (home \u002F app switcher)\n\n### Rule 6.3: Custom Gestures Must Be Discoverable\nIf you add a custom gesture, provide visual hints (e.g., a grabber handle) and ensure the action is also available through a visible button or menu item.\n\n### Rule 6.4: Support All Input Methods\nDesign for touch first, but also support:\n- Hardware keyboards (iPad keyboard accessories, Bluetooth keyboards)\n- Assistive devices (Switch Control, head tracking)\n- Pointer input (assistive touch)\n\n---\n\n## 7. Components\n**Impact:** HIGH\n\n### Rule 7.1: Button Styles\nUse the built-in button styles appropriately:\n- `.borderedProminent` — primary call-to-action\n- `.bordered` — secondary actions\n- `.borderless` — tertiary or inline actions\n- `.destructive` role — red tint for delete\u002Fremove\n\n**Correct:**\n```swift\nVStack(spacing: 16) {\n    Button(\"Purchase\") { buy() }\n        .buttonStyle(.borderedProminent)\n\n    Button(\"Add to Wishlist\") { wishlist() }\n        .buttonStyle(.bordered)\n\n    Button(\"Delete\", role: .destructive) { delete() }\n}\n```\n\n### Rule 7.2: Alerts — Critical Info Only\nUse alerts sparingly for critical information that requires a decision. Prefer 2 buttons; maximum 3. The destructive option should use `.destructive` role.\n\n**Correct:**\n```swift\n.alert(\"Delete Photo?\", isPresented: $showAlert) {\n    Button(\"Delete\", role: .destructive) { deletePhoto() }\n    Button(\"Cancel\", role: .cancel) { }\n} message: {\n    Text(\"This photo will be permanently removed.\")\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Alert for non-critical info — should be a banner or toast\n.alert(\"Tip\", isPresented: $showTip) {\n    Button(\"OK\") { }\n} message: {\n    Text(\"Swipe left to delete items.\")\n}\n```\n\n### Rule 7.3: Sheets for Scoped Tasks\nPresent sheets for self-contained tasks. Always provide a way to dismiss (close button or swipe down). Use `.presentationDetents()` for half-height sheets.\n\n```swift\n.sheet(isPresented: $showCompose) {\n    NavigationStack {\n        ComposeView()\n            .navigationTitle(\"New Message\")\n            .toolbar {\n                ToolbarItem(placement: .cancellationAction) {\n                    Button(\"Cancel\") { showCompose = false }\n                }\n                ToolbarItem(placement: .confirmationAction) {\n                    Button(\"Send\") { send() }\n                }\n            }\n    }\n    .presentationDetents([.medium, .large])\n}\n```\n\n### Rule 7.4: Lists — Inset Grouped Default\nUse the `.insetGrouped` list style as the default. Support swipe actions for common operations. Minimum row height is 44pt.\n\n**Correct:**\n```swift\nList {\n    Section(\"Recent\") {\n        ForEach(recentItems) { item in\n            ItemRow(item: item)\n                .swipeActions(edge: .trailing) {\n                    Button(role: .destructive) { delete(item) } label: {\n                        Label(\"Delete\", systemImage: \"trash\")\n                    }\n                    Button { archive(item) } label: {\n                        Label(\"Archive\", systemImage: \"archivebox\")\n                    }\n                    .tint(.blue)\n                }\n        }\n    }\n}\n.listStyle(.insetGrouped)\n```\n\n### Rule 7.5: Tab Bar Behavior\n- Use SF Symbols for tab icons — filled variant for the selected tab, outline for unselected\n- Never hide the tab bar when navigating deeper within a tab\n- Badge important counts with `.badge()`\n\n```swift\nTabView {\n    MessagesView()\n        .tabItem {\n            Label(\"Messages\", systemImage: \"message\")\n        }\n        .badge(unreadCount)\n}\n```\n\n### Rule 7.6: Search\nPlace search using `.searchable()`. Provide search suggestions and support recent searches.\n\n```swift\nNavigationStack {\n    List(filteredItems) { item in\n        ItemRow(item: item)\n    }\n    .searchable(text: $searchText, prompt: \"Search items\")\n    .searchSuggestions {\n        ForEach(suggestions) { suggestion in\n            Text(suggestion.title)\n                .searchCompletion(suggestion.title)\n        }\n    }\n}\n```\n\n### Rule 7.7: Context Menus\nUse context menus (long press) for secondary actions. Never use a context menu as the only way to access an action.\n\n```swift\nPhotoView(photo: photo)\n    .contextMenu {\n        Button { share(photo) } label: {\n            Label(\"Share\", systemImage: \"square.and.arrow.up\")\n        }\n        Button { favorite(photo) } label: {\n            Label(\"Favorite\", systemImage: \"heart\")\n        }\n        Button(role: .destructive) { delete(photo) } label: {\n            Label(\"Delete\", systemImage: \"trash\")\n        }\n    }\n```\n\n### Rule 7.8: Progress Indicators\n- Determinate (`ProgressView(value:total:)`) for operations with known duration\n- Indeterminate (`ProgressView()`) for unknown duration\n- Never block the entire screen with a spinner\n\n### Rule 7.9: SF Symbols — Rendering Modes\nUse the appropriate rendering mode for each symbol. Monochrome is the default; hierarchical, palette, and multicolor provide richer expression where appropriate. Always prefer the symbol rendering mode that best communicates meaning — do not default to monochrome when multicolor conveys critical state.\n\n**Correct:**\n```swift\n\u002F\u002F Hierarchical: single color with automatic opacity layers\nImage(systemName: \"person.crop.circle.fill\")\n    .symbolRenderingMode(.hierarchical)\n    .foregroundStyle(.blue)\n\n\u002F\u002F Multicolor: system-defined color per layer (e.g., battery, weather)\nImage(systemName: \"battery.100percent.bolt\")\n    .symbolRenderingMode(.multicolor)\n\n\u002F\u002F Palette: explicit per-layer colors\nImage(systemName: \"folder.badge.plus\")\n    .symbolRenderingMode(.palette)\n    .foregroundStyle(.white, .blue)\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Monochrome on a symbol that has meaningful multicolor layers\nImage(systemName: \"battery.100percent.bolt\")\n    .foregroundColor(.gray) \u002F\u002F loses the contextual color meaning\n```\n\n### Rule 7.10: SF Symbols — Weight and Scale\nMatch the symbol weight to adjacent text weight. Use scale variants (`.small`, `.medium`, `.large`) rather than resizing. The symbol weight should never appear heavier than adjacent text.\n\n**Correct:**\n```swift\nLabel(\"Download\", systemImage: \"arrow.down.circle.fill\")\n    .font(.body.weight(.semibold))\n    \u002F\u002F Symbol inherits .semibold weight automatically via Label\n```\n\n**Incorrect:**\n```swift\nHStack {\n    Image(systemName: \"arrow.down.circle.fill\")\n        .font(.system(size: 32)) \u002F\u002F explicit size ignores type scale\n    Text(\"Download\")\n        .font(.body)\n}\n```\n\n### Rule 7.11: SF Symbols — Animations (iOS 17+)\nUse `symbolEffect` for symbol state transitions. Prefer discrete effects (`.bounce`, `.pulse`) for actions and indefinite effects (`.variableColor`) for ongoing state. Do not use manual cross-fade between symbol names when `contentTransition(.symbolEffect)` is available.\n\n**Correct:**\n```swift\nImage(systemName: isLoading ? \"arrow.2.circlepath\" : \"checkmark.circle\")\n    .contentTransition(.symbolEffect(.replace))\n    .symbolEffect(.pulse, isActive: isLoading)\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F Manual opacity cross-fade between symbol names\nif isLoading {\n    Image(systemName: \"arrow.2.circlepath\")\n} else {\n    Image(systemName: \"checkmark.circle\")\n}\n```\n\n---\n\n## 8. Patterns\n**Impact:** MEDIUM\n\n### Rule 8.1: Onboarding — Max 3 Pages, Skippable\nKeep onboarding to 3 or fewer pages. Always provide a skip option. Defer sign-in until the user needs authenticated features.\n\n```swift\nTabView {\n    OnboardingPage(\n        image: \"wand.and.stars\",\n        title: \"Smart Suggestions\",\n        subtitle: \"Get personalized recommendations based on your preferences.\"\n    )\n    OnboardingPage(\n        image: \"bell.badge\",\n        title: \"Stay Updated\",\n        subtitle: \"Receive notifications for things that matter to you.\"\n    )\n    OnboardingPage(\n        image: \"checkmark.shield\",\n        title: \"Private & Secure\",\n        subtitle: \"Your data stays on your device.\"\n    )\n}\n.tabViewStyle(.page)\n.overlay(alignment: .topTrailing) {\n    Button(\"Skip\") { completeOnboarding() }\n        .padding()\n}\n```\n\n### Rule 8.2: Loading — Skeleton Views, No Blocking Spinners\nUse skeleton\u002Fplaceholder views that match the layout of the content being loaded. Never show a full-screen blocking spinner.\n\n**Correct:**\n```swift\nif isLoading {\n    ForEach(0..\u003C5) { _ in\n        SkeletonRow() \u002F\u002F Placeholder matching final row layout\n            .redacted(reason: .placeholder)\n    }\n} else {\n    ForEach(items) { item in\n        ItemRow(item: item)\n    }\n}\n```\n\n**Incorrect:**\n```swift\nif isLoading {\n    ProgressView(\"Loading...\") \u002F\u002F Blocks the entire view\n} else {\n    List(items) { item in ItemRow(item: item) }\n}\n```\n\n### Rule 8.3: Launch Screen — Match First Screen\nThe launch storyboard must visually match the initial screen of the app. No splash logos, no branding screens. This creates the perception of instant launch.\n\n### Rule 8.4: Modality — Use Sparingly\nPresent modal views only when the user must complete or abandon a focused task. Always provide a clear dismiss action. Never stack modals on top of modals.\n\n### Rule 8.5: Notifications — High Value Only\nOnly send notifications for content the user genuinely cares about. Support actionable notifications. Categorize notifications so users can control them granularly.\n\n### Rule 8.6: Settings Placement\n- **Frequent settings:** In-app settings screen accessible from a profile or gear icon\n- **Privacy\u002Fpermission settings:** Defer to the system Settings app via URL scheme\n- Never duplicate system-level controls in-app\n\n### Rule 8.7: Feedback — Visual + Haptic\nProvide immediate feedback for every user action:\n- Visual state change (button highlight, animation)\n- Haptic feedback for significant actions using `UIImpactFeedbackGenerator`, `UINotificationFeedbackGenerator`, or `UISelectionFeedbackGenerator`\n\n```swift\nButton(\"Complete\") {\n    let generator = UINotificationFeedbackGenerator()\n    generator.notificationOccurred(.success)\n    completeTask()\n}\n```\n\n### Rule 8.8: Show Waiting States Immediately\nIf an action cannot complete immediately, acknowledge the tap at once, then show inline progress, skeletons, or partial results. Never leave the interface visually unchanged while work continues.\n\n---\n\n## 9. Privacy & Permissions\n**Impact:** HIGH\n\n### Rule 9.1: Request Permissions in Context\nRequest a permission at the moment the user takes an action that needs it — never at app launch.\n\n**Correct:**\n```swift\nButton(\"Take Photo\") {\n    \u002F\u002F Request camera permission only when the user taps this button\n    AVCaptureDevice.requestAccess(for: .video) { granted in\n        if granted { showCamera = true }\n    }\n}\n```\n\n**Incorrect:**\n```swift\n\u002F\u002F In AppDelegate.didFinishLaunching — too early, no context\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions ...) {\n    AVCaptureDevice.requestAccess(for: .video) { _ in }\n    CLLocationManager().requestWhenInUseAuthorization()\n    UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { _, _ in }\n}\n```\n\n### Rule 9.2: Explain Before System Prompt\nShow a custom explanation screen before triggering the system permission dialog. The system dialog only appears once — if the user denies, the app must direct them to Settings.\n\n```swift\nstruct LocationExplanation: View {\n    var body: some View {\n        VStack(spacing: 16) {\n            Image(systemName: \"location.fill\")\n                .font(.largeTitle)\n            Text(\"Find Nearby Stores\")\n                .font(.headline)\n            Text(\"We use your location to show stores within walking distance. Your location is never shared or stored.\")\n                .font(.body)\n                .multilineTextAlignment(.center)\n            Button(\"Enable Location\") {\n                locationManager.requestWhenInUseAuthorization()\n            }\n            .buttonStyle(.borderedProminent)\n            Button(\"Not Now\") { dismiss() }\n                .foregroundStyle(.secondary)\n        }\n        .padding()\n    }\n}\n```\n\n### Rule 9.3: Support Sign in with Apple\nIf the app offers any third-party sign-in (Google, Facebook), it must also offer Sign in with Apple. Present it as the first option.\n\n### Rule 9.4: Don't Require Accounts Unless Necessary\nLet users explore the app before requiring sign-in. Gate only features that genuinely need authentication (purchases, sync, social features).\n\n### Rule 9.5: App Tracking Transparency\nIf you track users across apps or websites, display the ATT prompt. Respect denial — do not degrade the experience for users who opt out.\n\n### Rule 9.6: Location Button for One-Time Access\nUse `LocationButton` for actions that need location once without requesting ongoing permission.\n\n```swift\nimport CoreLocationUI\n\nLocationButton(.currentLocation) {\n    fetchNearbyStores()\n}\n.labelStyle(.titleAndIcon)\n```\n\n---\n\n## 10. System Integration\n**Impact:** MEDIUM\n\n### Rule 10.1: Widgets for Glanceable Data\nProvide widgets using WidgetKit for information users check frequently. Show the most useful snapshot. Since iOS 17, widgets support interactive controls: use `Button` and `Toggle` backed by App Intents for actions users perform directly from the widget without opening the app.\n\n```swift\n\u002F\u002F iOS 17+ interactive widget with a Button\nstruct TimerWidgetView: View {\n    let entry: TimerEntry\n\n    var body: some View {\n        VStack {\n            Text(entry.remaining, style: .timer)\n                .font(.title2.bold())\n            Button(intent: ToggleTimerIntent()) {\n                Label(entry.isRunning ? \"Pause\" : \"Start\",\n                      systemImage: entry.isRunning ? \"pause.fill\" : \"play.fill\")\n            }\n            .buttonStyle(.borderedProminent)\n        }\n    }\n}\n```\n\n### Rule 10.2: App Shortcuts for Key Actions\nDefine App Shortcuts so users can trigger key actions from Siri, Spotlight, and the Shortcuts app.\n\n```swift\nstruct MyAppShortcuts: AppShortcutsProvider {\n    static var appShortcuts: [AppShortcut] {\n        AppShortcut(\n            intent: StartWorkoutIntent(),\n            phrases: [\"Start a workout in \\(.applicationName)\"],\n            shortTitle: \"Start Workout\",\n            systemImageName: \"figure.run\"\n        )\n    }\n}\n```\n\n### Rule 10.3: Spotlight Indexing\nIndex app content with `CSSearchableItem` so users can find it from Spotlight search.\n\n### Rule 10.4: Share Sheet Integration\nSupport the system share sheet for content that users might want to send elsewhere. Implement `UIActivityItemSource` or use `ShareLink` in SwiftUI.\n\n```swift\nShareLink(item: article.url) {\n    Label(\"Share\", systemImage: \"square.and.arrow.up\")\n}\n```\n\n### Rule 10.5: Live Activities\nUse Live Activities and the Dynamic Island for real-time, time-bound events (delivery tracking, sports scores, workouts).\n\n### Rule 10.6: Handle Interruptions Gracefully\nSave state and pause gracefully when interrupted by:\n- Phone calls\n- Siri invocations\n- Notifications\n- App switcher\n- FaceTime SharePlay\n\nUse `scenePhase` to detect transitions:\n\n```swift\n@Environment(\\.scenePhase) var scenePhase\n\n.onChange(of: scenePhase) { _, newPhase in\n    switch newPhase {\n    case .active: resumeActivity()\n    case .inactive: pauseActivity()\n    case .background: saveState()\n    @unknown default: break\n    }\n}\n```\n\n---\n\n## Quick Reference\n\n| Need | Component | Notes |\n|------|-----------|-------|\n| Top-level sections (3-5) | `TabView` with `.tabItem` | Bottom tab bar, SF Symbols |\n| Hierarchical drill-down | `NavigationStack` | Large title on root, inline on children |\n| Self-contained task | `.sheet` | Swipe to dismiss, cancel\u002Fdone buttons |\n| Critical decision | `.alert` | 2 buttons preferred, max 3 |\n| Secondary actions | `.contextMenu` | Long press; must also be accessible elsewhere |\n| Scrolling content | `List` with `.insetGrouped` | 44pt min row, swipe actions |\n| Text input | `TextField` \u002F `TextEditor` | Label above, validation below |\n| Selection (few options) | `Picker` | Segmented for 2-5, wheel for many |\n| Selection (on\u002Foff) | `Toggle` | Aligned right in a list row |\n| Search | `.searchable` | Suggestions, recent searches |\n| Progress (known) | `ProgressView(value:total:)` | Show percentage or time remaining |\n| Progress (unknown) | `ProgressView()` | Inline, never full-screen blocking |\n| One-time location | `LocationButton` | No persistent permission needed |\n| Sharing content | `ShareLink` | System share sheet |\n| Haptic feedback | `UIImpactFeedbackGenerator` | `.light`, `.medium`, `.heavy` |\n| Destructive action | `Button(role: .destructive)` | Red tint, confirm via alert |\n\n---\n\n## Evaluation Checklist\n\nUse this checklist to audit an iPhone app for HIG compliance:\n\n### Layout & Safe Areas\n- [ ] All touch targets are at least 44x44pt\n- [ ] No content is clipped under status bar, Dynamic Island, or home indicator\n- [ ] Primary actions are in the bottom half of the screen (thumb zone)\n- [ ] Layout adapts from iPhone SE to Pro Max without breaking\n- [ ] Spacing aligns to the 8pt grid\n\n### Navigation\n- [ ] Tab bar is used for 3-5 top-level sections\n- [ ] No hamburger\u002Fdrawer menus\n- [ ] Primary views use large titles\n- [ ] Swipe-from-left-edge back navigation works throughout\n- [ ] State is preserved when switching tabs\n\n### Typography\n- [ ] All text uses built-in text styles or custom fonts scaled with Dynamic Type (`Font.custom(_:size:relativeTo:)` in SwiftUI or `UIFontMetrics` in UIKit)\n- [ ] Dynamic Type is supported up to accessibility sizes\n- [ ] Layouts reflow at large text sizes (no truncation of essential text)\n- [ ] Minimum text size is 11pt\n\n### Color & Dark Mode\n- [ ] App uses semantic system colors or provides light\u002Fdark asset variants\n- [ ] Dark Mode looks intentional (not just inverted)\n- [ ] No information conveyed by color alone\n- [ ] Text contrast meets 4.5:1 (normal) or 3:1 (large)\n- [ ] Single accent color for interactive elements\n\n### Accessibility\n- [ ] VoiceOver reads all screens logically with meaningful labels\n- [ ] Bold Text preference is respected\n- [ ] Reduce Motion disables decorative animations\n- [ ] Increase Contrast variant exists for custom colors\n- [ ] All gestures have alternative access paths\n\n### Components\n- [ ] Alerts are used only for critical decisions\n- [ ] Sheets have a dismiss path (button and\u002For swipe)\n- [ ] List rows are at least 44pt tall\n- [ ] Tab bar is never hidden during navigation\n- [ ] Destructive buttons use the `.destructive` role\n\n### Privacy\n- [ ] Permissions are requested in context, not at launch\n- [ ] Custom explanation shown before each system permission dialog\n- [ ] Sign in with Apple offered alongside other providers\n- [ ] App is usable without an account for basic features\n- [ ] ATT prompt is shown if tracking, and denial is respected\n\n### System Integration\n- [ ] Widgets show glanceable, up-to-date information\n- [ ] App content is indexed for Spotlight\n- [ ] Share Sheet is available for shareable content\n- [ ] App handles interruptions (calls, background, Siri) gracefully\n\n---\n\n## Anti-Patterns\n\nThese are common mistakes that violate the iOS Human Interface Guidelines. Never do these:\n\n1. **Hamburger menus** — Use a tab bar. Hamburger menus hide navigation and reduce feature discoverability by up to 50%.\n\n2. **Custom back buttons that break swipe-back** — If you replace the back button, ensure the swipe-from-left-edge gesture still works via `NavigationStack`.\n\n3. **Full-screen blocking spinners** — Use skeleton views or inline progress indicators. Blocking spinners make the app feel frozen.\n\n4. **Splash screens with logos** — The launch screen must mirror the first screen of the app. Branding delays feel artificial.\n\n5. **Requesting all permissions at launch** — Asking for camera, location, notifications, and contacts on first launch guarantees most will be denied.\n\n6. **Hardcoded font sizes** — Use text styles. Hardcoded sizes ignore Dynamic Type and accessibility preferences, breaking the app for millions of users.\n\n7. **Using only color to indicate state** — Red\u002Fgreen for valid\u002Finvalid excludes colorblind users. Always pair with icons or text.\n\n8. **Alerts for non-critical information** — Alerts interrupt flow and require dismissal. Use banners, toasts, or inline messages for tips and non-critical information.\n\n9. **Hiding the tab bar on push** — Tab bars should remain visible throughout navigation within a tab. Hiding them disorients users.\n\n10. **Ignoring safe areas** — Using `.ignoresSafeArea()` on content views causes text and buttons to disappear under the notch, Dynamic Island, or home indicator.\n\n11. **Non-dismissable modals** — Every modal must have a clear dismiss path (close button, cancel, swipe down). Trapping users in a modal is hostile.\n\n12. **Custom gestures without alternatives** — A three-finger swipe for undo is unusable for many people. Provide a visible button or menu item as well.\n\n13. **Tiny touch targets** — Buttons and links smaller than 44pt cause mis-taps, especially in lists and toolbars.\n\n14. **Stacked modals** — Presenting a sheet on top of a sheet on top of a sheet creates navigation confusion. Use navigation within a single modal instead.\n\n15. **Dark Mode as an afterthought** — Using hardcoded colors means the app is either broken in Dark Mode or light mode. Always use semantic colors.\n",{"name":5,"license":15,"metadata":54,"description":7},{"author":55,"version":56},"platform-design-skills","1.0.0","main","7a940b8e5af16c95e5c190a524421d32fc7445a9",".claude\u002Fskills\u002Fios","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui","https:\u002F\u002Fneuroads.com.br","2026-08-12 00:42:44",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add headmktlucca-ui\u002Fneuroads-lp --skill ios-design-guidelines",{"tool":70,"command":71},"bunx","bunx skills add headmktlucca-ui\u002Fneuroads-lp --skill ios-design-guidelines",{"tool":73,"command":74},"pnpm","pnpm dlx skills add headmktlucca-ui\u002Fneuroads-lp --skill ios-design-guidelines","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fios\u002FSKILL.md",true,[78,84,88],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"AGENTS.md","REFERENCE","参考文档",1820,"https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fios\u002FAGENTS.md",{"path":85,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":86,"sourceHtmlUrl":87},"metadata.json",698,"https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fios\u002Fmetadata.json",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"rules\u002F_sections.md",4663,"https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fios\u002Frules\u002F_sections.md",[93,115,138,162,180,206],{"skillNo":94,"name":95,"nameZh":6,"description":96,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":105,"publishedAt":49},"01M6PTDCZTXTGSTX4RFK901YSZ","xunit-skill","Generates xUnit.net tests in C#. Covers Fact\u002FTheory, constructor injection, IClassFixture, and FluentAssertions. Use when user mentions \"xUnit\", \"[Fact]\", \"[Theory]\", \"Assert.Equal\", \"C# xUnit\". Triggers on: \"xUnit\", \"[Fact]\", \"[Theory]\", \"Assert.Equal C#\", \"xUnit.net\".","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fxunit-skill\u002FSKILL.md",[99,102],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":103,"categoryName":104,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[106,107,108,109,110,43,111,41,112,113,114],"xunit","xunit skill","testing","injection","user","tests","unit","unit-testing","C#",{"skillNo":116,"name":117,"nameZh":6,"description":118,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":123,"publishedAt":49},"01M6PTCB59ZTG5FTMWGPYEMMBP","xcuitest-skill","Generates XCUITest UI tests for iOS\u002FiPadOS apps in Swift. Apple's native testing framework for reliable, fast UI automation. Supports local simulators and TestMu AI cloud real devices. Use when user mentions \"XCUITest\", \"XCTest\", \"iOS UI test\", \"Swift test\", \"XCUIApplication\". Triggers on: \"XCUITest\", \"XCTest UI\", \"iOS UI test\", \"Swift UI test\", \"XCUIApplication\", \"TestMu\".","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fxcuitest-skill\u002FSKILL.md",[121,122],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":103,"categoryName":104,"taxonomyType":21,"taxonomyTypeDesc":22},[124,125,108,126,127,128,30,129,43,130,131,132,133,134,111,41,135,136,36,137],"xcuitest","xcuitest skill","automation","test","testing framework","devices","mobile","mobile-testing","ui test","local","apple","apps","native","objective",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":146,"publishedAt":49},"01M6PTATMNTY9NF4J7KSR76YK6","world-builder","Create and manage generative game zones for Dorothy's Pokemon-style world. Use this skill when creating, updating, or designing game worlds via MCP tools.","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fworld-builder\u002FSKILL.md",[144,145],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":103,"categoryName":104,"taxonomyType":21,"taxonomyTypeDesc":22},[147,148,149,150,151,152,153,154,43,155,156,157,158,159,160,36,161],"builder","world","world builder","MCP","create","mcp tools","Tools","game","pokemon","manage","style","via","theme","zone","generative",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":170,"publishedAt":49},"01M6PT9Z92JTSQEHE1QR0NNW5F","webdriverio-skill","Generates WebdriverIO (WDIO) automation tests in JavaScript or TypeScript. Supports local and TestMu AI cloud. Use when user mentions \"WebdriverIO\", \"WDIO\", \"wdio.conf\", \"browser.url\", \"$\", \"$$\". Triggers on: \"WebdriverIO\", \"WDIO\", \"wdio\", \"browser.$\".","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fwebdriverio-skill\u002FSKILL.md",[168,169],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":103,"categoryName":104,"taxonomyType":21,"taxonomyTypeDesc":22},[171,108,172,126,173,174,175,176,110,43,177,133,111,41,178,179],"webdriverio skill","typescript","javascript","browser","cloud","e2e","e2e-testing","URL","conf",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":184,"categories":185,"keywords":191,"publishedAt":49},"01M6PT9NVK36ZWCQ69YXK3FR4K","web-design-guidelines","Web platform design and accessibility guidelines. Use when building web interfaces, auditing accessibility, implementing responsive layouts, or reviewing web UI code. Triggers on tasks involving HTML, CSS, web components, WCAG compliance, responsive design, or web performance.","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fweb\u002FSKILL.md",[186,189,190],{"categoryNo":187,"categoryName":188,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},[33,192,29,193,194,195,196,48,197,198,199,200,47,201,202,203,204,34,41,205],"web","web design guidelines","web design","css","performance","web components","tasks","code","web performance","platform design","html","responsive design","responsive","implementing",{"skillNo":207,"name":208,"nameZh":6,"description":209,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":210,"categories":211,"keywords":214,"publishedAt":49},"01M6PT9A3JKRE3X34276F2FW5R","watchos-design-guidelines","Apple Human Interface Guidelines for Apple Watch. Use when building watchOS apps, complications, or workout features. Triggers on tasks involving Watch UI, Digital Crown, glanceable interfaces, or wrist-based interactions.","https:\u002F\u002Fgithub.com\u002Fheadmktlucca-ui\u002Fneuroads-lp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fwatchos\u002FSKILL.md",[212,213],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},[33,29,215,198,47,43,216,42,134,34,217,41,135,38,36,218,219],"watchos design guidelines","workout","watch","platform-design","features"]