ios-hig-design
Design native iOS interfaces following Apple Human Interface Guidelines. Use when the user mentions "iPhone app", "iPad layout", "SwiftUI", "UIKit", "Dynamic Island", "safe areas", "HIG compliance", "SF Symbols", "haptic feedback", "iOS accessibility", "make my app feel native",
Install
npx skills add https://github.com/wondelai/skills/tree/main/plugins/wondelai-skills/skills/ios-hig-design
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wondelai-skills@llmmart
git clone https://github.com/wondelai/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wondelai/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
iOS Human Interface Guidelines Design Skill
Framework for designing native iOS interfaces that feel intuitive, consistent, and aligned with Apple's design philosophy. Based on Apple's Human Interface Guidelines, the definitive resource for apps that integrate seamlessly with iPhone, iPad, and the Apple ecosystem.
Core Principle
Apple's iOS design philosophy rests on three pillars: clarity (every element legible and purposeful), deference (the interface never overshadows the content it presents), and depth (layering, transitions, and realistic motion convey hierarchy and spatial relationships).
The foundation: The best iOS apps internalize this philosophy rather than following HIG rules mechanically. Native components, system conventions, and platform consistency aren't constraints---they're the reason iOS users trust and enjoy apps that feel like they belong.
Scoring
Goal: 10/10. Score 1 point per satisfied row of the Quick Diagnostic (6 rows), plus up to 4 points for native idiom: +1 semantic colors/text styles throughout (no hardcoded values), +1 system controls over custom reimplementations, +1 standard gestures and meaningful haptics, +1 SF Symbols and correct app-icon shape. Bands: 9-10 = native, accessible, adapts to Dark Mode and Dynamic Type, zero foreign patterns; 5-6 = works but leaks Android idioms or hardcodes color/size; <=3 = fails safe areas, touch targets, or VoiceOver. Always state the score and the specific improvements needed to reach 10/10.
iOS Design Framework
1. Layout & Safe Areas
Core concept: iOS devices have specific screen dimensions, safe area insets, and hardware intrusions (notch, Dynamic Island, home indicator) that every layout must respect.
Key insights:
- Design for the smallest screen first (375pt width, iPhone SE)
- Safe areas protect content from the notch, Dynamic Island, and home indicator---never place interactive elements under them
- Standard content margins: 16-20pt from screen edges; spacing increments: 8 / 16 / 24pt
- Minimum touch target and list row height: 44pt
Product applications:
| Context | Layout Pattern | Example |
|---|---|---|
| Status bar | 20pt classic, 44-54pt on Dynamic Island devices | Time, signal, battery area |
| Navigation bar | 44pt standard row + ~52pt large title (~96pt total) | Back button, title, actions |
| Content area | Flexible, scrollable, respects safe area | Main app content |
| Tab bar | 49pt height, translucent with blur | 2-5 primary destinations |
| Home indicator | 34pt inset at bottom | System gesture area |
Copy patterns:
- Use
VStack { }, which respects safe areas by default - Use
.ignoresSafeArea()only for backgrounds and decoration, never interactive content - Test on multiple sizes, including iPhone SE and Pro Max
See references/navigation.md when laying out chrome---exact nav bar and tab bar dimensions, large-title behavior, and split-view rules.
2. Typography & Dynamic Type
Core concept: iOS uses the San Francisco (SF Pro) typeface with semantic text styles that automatically scale for accessibility via Dynamic Type. Semantic styles give consistent platform hierarchy; Dynamic Type lets users read at their preferred size without breaking layouts.
Key insights:
- Large Title: 34pt Bold; Title: 17pt Medium; Body: 17pt Regular; Caption: 12-13pt; secondary text: 15pt at 60% opacity
- Minimum text size 11pt (captions/secondary only)
- Line height at least 1.3x font size; optimal line length 35-50 characters on mobile
- Always left-aligned, non-justified text
Product applications:
| Context | Typography Pattern | Example |
|---|---|---|
| Screen titles | .largeTitle or .title style |
Large title collapses on scroll |
| Body content | .body style, 17pt |
List items, descriptions |
| Secondary info | .subheadline or .footnote |
Timestamps, metadata |
| Tab labels | 10pt SF text | Tab bar item labels |
| Buttons | .body weight semibold |
Primary action text |
Copy patterns:
- Use
.font(.title),.font(.body),.font(.caption)instead of hardcoded sizes;@ScaledMetricfor custom spacing that scales - Prefer weight and color variation over extreme size differences for hierarchy
- Test all layouts at the largest Dynamic Type size
See references/typography.md when matching a design to exact specs---per-style hex values and the Dark Mode text-color mapping.
3. Color & Dark Mode
Core concept: iOS provides semantic system colors that automatically adapt between light and dark appearances while preserving contrast and hierarchy.
Key insights:
- Use
Color(.label),Color(.secondaryLabel),Color(.systemBackground)instead of hardcoded colors Color(.systemBlue)is the default tint;.systemRedfor destructive actions;.systemGreenfor success- Dark Mode inverts text colors and shifts backgrounds darker while keeping relative hierarchy; accent colors need lower brightness and higher saturation to pop
- Maintain 4.5:1 contrast in both modes; preview both during development
Product applications:
| Context | Color Pattern | Example |
|---|---|---|
| Primary text | Color(.label) |
Adapts white/black per mode |
| Secondary text | Color(.secondaryLabel) |
60% opacity in both modes |
| Backgrounds | Color(.systemBackground) / .secondarySystemBackground |
Layered depth |
| Destructive actions | Color(.systemRed) |
Delete buttons, warnings |
| Interactive tint | App accent color or .systemBlue |
Links, toggle states |
Copy patterns:
- Use
.preferredColorScheme(.light)and.darkin previews to test both modes side by side - Define custom colors in the Asset Catalog with light/dark variants, not in code
- Never assume a background is white or black; test with Increase Contrast enabled
See references/colors-depth.md when checking contrast---the full WCAG ratio table (normal text, large text, UI components) and the tertiary/grouped-background tokens.
4. Navigation Patterns
Core concept: iOS uses a layered navigation model: tab bars for primary destinations, navigation stacks for hierarchical drilling, and modals for focused tasks. Users rely on these patterns to know where they are and how to get back; reinventing them makes the app feel foreign.
Key insights:
- Tab bar: 2-5 primary destinations, always visible, remembers state per tab
- Navigation bar: back button (top-left), title (center or large), actions (top-right); large title collapses on scroll
- Modals for focused tasks; dismiss via swipe-down or explicit close button
- Never use hamburger menus---iOS users expect tab bars
- Search bar can sit below the nav bar, hidden until pulled down
Product applications:
| Context | Navigation Pattern | Example |
|---|---|---|
| App structure | Tab bar with 3-5 tabs | Home, Search, Profile |
| Content hierarchy | Push navigation (drill-down) | List > Detail > Edit |
| Focused tasks | Modal presentation | Compose, settings, filters |
| Search | Pull-down search bar | Spotlight-style search |
| Split view | iPad sidebar + detail | Mail, Notes on iPad |
Copy patterns:
- Back button text should be the previous screen's title, not "Back"
- Tab labels are single words ("Home", "Search"); modal titles describe the task ("New Message", "Edit Profile")
- Use
NavigationStack(not deprecatedNavigationView) in SwiftUI
5. Controls & Inputs
Core concept: iOS provides a rich library of native controls (buttons, lists, toggles, pickers, menus, text fields) that users already understand and expect.
Why it works: Native controls ship with built-in accessibility, haptics, and learned interaction patterns; custom controls create friction and miss edge cases Apple already solved.
Key insights:
- Page-level actions go in the nav bar (top) or action bar (bottom)
- Primary buttons are filled with the theme color; secondary are outlined or text-only
- Destructive actions use red and require confirmation when irreversible
- Lists (table views) are the fundamental iOS content pattern
- Match keyboard type to input (
.emailAddress,.phonePad,.URL); use.textContentTypefor autofill
Product applications:
| Context | Control Pattern | Example |
|---|---|---|
| Forms | Native text fields with proper keyboard types | Email field with @ keyboard |
| Settings | Grouped list with toggles, disclosure | iOS Settings style |
| Selection | Picker, segmented control, or action sheet | Date picker, sort options |
| Destructive actions | Red button + confirmation alert | "Delete Account" flow |
| Context actions | Long press menu or swipe actions | Edit, share, delete on row |
Copy patterns:
- Pair
.keyboardType(.emailAddress)with.textContentType(.emailAddress) - Prefer system confirmations:
.alert()or.confirmationDialog(); use.swipeActionson list rows - Place primary action buttons at the bottom of the screen within thumb reach
Ethical boundary: Never disguise ads as native controls or make destructive actions easy to trigger accidentally.
See references/components.md when building a specific control---button styles, list/section variants, picker vs segmented-control choice, and confirmation-dialog wiring. See references/keyboard-input.md when building forms---keyboard-type table, input accessory views, and hardware-keyboard shortcuts.
6. Accessibility
Core concept: iOS has world-class accessibility features (VoiceOver, Dynamic Type, Switch Control, Voice Control), and every app must support them as a first-class concern. App Store review can reject apps that are unusable with assistive technologies.
Key insights:
- Every interactive element needs an
.accessibilityLabel; use.accessibilityValuefor state and.accessibilityHintfor effect - Group related elements with
.accessibilityElement(children: .combine) - Support Dynamic Type at all sizes; test at the largest setting
- Honor the 44 x 44pt touch target (section 1) and 4.5:1 contrast minimum (section 3) as accessibility requirements, not just visual defaults
- Never convey meaning through color alone
Product applications:
| Context | Accessibility Pattern | Example |
|---|---|---|
| Icons | .accessibilityLabel("Favorite") |
Heart icon with label |
| Sliders | .accessibilityValue("\(Int(volume * 100))%") |
Volume control |
| Buttons | .accessibilityHint("Shares this item") |
Share button |
| Groups | .accessibilityElement(children: .combine) |
Avatar + name row |
| Images | Decorative: .accessibilityHidden(true) |
Background patterns |
Copy patterns:
- Write labels as nouns ("Favorite", "Settings"); write hints as actions ("Shares this item with others")
- Test the complete app flow using only VoiceOver
- Use Xcode's Accessibility Inspector to audit contrast and labels
See references/accessibility.md before sign-off---the full VoiceOver-implementation patterns and a pre-ship accessibility checklist to run the app against.
7. Icons & Images
Core concept: iOS uses SF Symbols as the standard icon system and requires app icons in specific sizes with the signature superellipse ("squircle") mask applied automatically. SF Symbols align optically with San Francisco text and scale with Dynamic Type, so they stay aligned and crisp at every weight and size.
Key insights:
- Use SF Symbols (
Image(systemName:)) for all standard icons---they scale with text - App icons: export 1024x1024px square; iOS applies the squircle mask (corner radius = side x 0.222 with 61% smoothing)
- iOS 18+ supports light, dark, and tinted icon variants
- Avoid text in app icons; keep designs simple with recognizable silhouettes
Product applications:
| Context | Icon Pattern | Example |
|---|---|---|
| Tab bar | SF Symbols, filled variant for selected | house.fill, magnifyingglass |
| Navigation bar | SF Symbols at regular weight | gear, plus, ellipsis |
| List accessories | SF Symbols, secondary color | chevron.right, checkmark |
| App icon | 1024px square, simple bold design | Single recognizable glyph |
Copy patterns:
- Use
Image(systemName: "heart.fill"); apply.symbolRenderingMode(.hierarchical)for multi-color depth - Size symbols relative to text with
.imageScale(.large)or.font() - Browse symbols in the free SF Symbols app from Apple
Ethical boundary: Never use icons that suggest functionality that doesn't exist or contradict iOS conventions (trash = delete, not archive).
See references/app-icons.md when exporting the app icon---per-context size table, exact squircle math, and the iOS 18 light/dark/tinted variant requirements.
8. Gestures & Haptics
Core concept: iOS defines standard gestures (swipe back, pull to refresh, long press for context menu) and haptic feedback patterns that must be respected and never overridden. Gestures are muscle memory---repurposing swipe-back or pull-to-refresh disorients users; haptics give invisible confirmation that an action registered.
Key insights:
- Never override: swipe-right-from-edge (back), swipe-down on modal (dismiss), pull-down on list (refresh)
- Swipe-left on rows reveals actions; long press shows context menus; pinch zooms images and maps
- Three haptic types: impact (physical actions), notification (outcomes), selection (UI changes)
- Haptics should be subtle and meaningful---never constant or annoying
Product applications:
| Context | Gesture/Haptic Pattern | Example |
|---|---|---|
| Navigation | Swipe right from left edge | System back gesture |
| Modals | Swipe down to dismiss | Sheet dismissal |
| Lists | Pull to refresh, swipe for actions | Refresh content, delete row |
| Confirmation | .success haptic on completion |
Payment confirmed |
| Selection | Selection haptic on toggle/pick | Picker wheel scroll |
Copy patterns:
UIImpactFeedbackGenerator(style: .medium)for physical interactions;UISelectionFeedbackGenerator()for UI state changesUINotificationFeedbackGenerator()with.success,.warning,.errorfor outcomes- Call
.prepare()before triggering haptics to minimize latency
See references/gestures.md when wiring gestures or animation---the full reserved-gesture table, haptic-generator recipes, and standard animation timing/curves.
Common Mistakes
| Mistake | Why It Fails | Fix |
|---|---|---|
| Overriding standard gestures | Breaks muscle memory for swipe-back, pull-refresh | Use system gestures as intended; custom gestures only for supplementary actions |
| Touch targets under 44pt | Mis-taps, frustration, accessibility failures | Make all interactive elements at least 44 x 44pt |
| Ignoring safe areas | Content hidden behind notch, Dynamic Island, home indicator | Respect safe area insets; .ignoresSafeArea() only for backgrounds |
| Using Android patterns on iOS | Hamburger menus, top tabs, FABs feel foreign | Use tab bars, bottom sheets, native iOS components |
| Skipping Dark Mode | Broken layouts, unreadable text for Dark Mode users | Use semantic colors; test both appearances |
| Hardcoding font sizes | Breaks Dynamic Type, excludes low-vision users | Use semantic text styles (.title, .body, .caption) throughout |
| Low contrast text | Fails WCAG AA; unreadable in sunlight | Maintain 4.5:1 minimum; test with Increase Contrast |
| Not testing on real devices | Simulator misses performance, haptics, safe area edge cases | Test on physical devices at smallest and largest sizes |
Quick Diagnostic
Audit any iOS interface design:
| Question | If No | Action |
|---|---|---|
| Does the layout respect safe areas on all device sizes? | Content hidden behind hardware | Audit on iPhone SE and Pro Max; fix insets |
| Are all touch targets at least 44 x 44pt? | Mis-taps and accessibility failures | Increase tap areas; .frame(minWidth: 44, minHeight: 44) |
| Does the app work fully in Dark Mode? | Broken/unreadable UI for Dark Mode users | Replace hardcoded colors with semantic system colors |
| Does text scale properly with Dynamic Type? | Excludes low-vision users | Use semantic text styles; test at largest setting |
| Can a VoiceOver user complete every task? | App inaccessible to blind users | Add labels, values, hints to all interactive elements |
| Are navigation patterns native iOS? | App feels foreign | Replace hamburger menus with tab bars; standard push/modal navigation |
Beyond Core UI
The eight framework sections above each link their deep-dive reference inline at the point of need. Three further references cover system surfaces that sit outside the on-screen UI:
- See references/privacy-permissions.md when the app requests camera, location, contacts, or any protected resource---request timing, pre-permission priming screens, usage-string wording, and the denied-permission recovery path.
- See references/widgets-extensions.md when building a Home Screen widget, App Clip, Live Activity, or share/action extension---supported sizes and per-surface design constraints.
- See references/system-integration.md when wiring the app into the OS---Siri/Shortcuts intents, Handoff, drag-and-drop, universal links, and Spotlight indexing.
Further Reading
For the complete guidelines, platform-specific guidance, and latest updates:
- Apple Human Interface Guidelines --- the definitive reference for all Apple platforms
- SF Symbols --- Apple's icon system, 5,000+ configurable symbols
- Apple Design Resources --- official Figma/Sketch templates and UI kits
- WWDC Design Sessions --- videos on design principles and new features
- "Designed by Apple in California" --- photo book of Apple's design process (out of print; Apple no longer sells it)
- "The Design of Everyday Things" by Don Norman --- the human-centered design text that influenced Apple
- "Universal Principles of Design" by William Lidwell, Kritina Holden, and Jill Butler --- 125 principles applicable to iOS
About the Author
The Apple Human Interface Guidelines are written and maintained by Apple's Human Interface Design team, one of the most influential design organizations in technology. First published in 1984 alongside the original Macintosh, the HIG established principles---direct manipulation, consistency, user control---that defined graphical interface design and have evolved through iPhone, iPad, Apple Watch, and Vision Pro. It remains freely available at developer.apple.com as the essential reference for Apple platforms.
Files (skills)
-
references
-
accessibility.md 1.3 KB
# iOS Accessibility ## VoiceOver Support Every interactive element needs accessibility labels: ```swift // Accessible label (what it is) Image(systemName: "heart.fill") .accessibilityLabel("Favorite") // Accessible value (current state) Slider(value: $volume) .accessibilityLabel("Volume") .accessibilityValue("\(Int(volume * 100))%") // Accessible hint (what it does) Button("Share") { share() } .accessibilityHint("Shares this item with others") // Group related elements HStack { Image(systemName: "person") Text("John Doe") } .accessibilityElement(children: .combine) ``` ## Dynamic Type Support user font size preferences: ```swift // Use semantic text styles (automatically scales) Text("Content") .font(.body) // For custom fonts, scale with Dynamic Type @ScaledMetric var customSize: CGFloat = 16 Text("Custom") .font(.system(size: customSize)) ``` ## High Contrast Mode ```swift @Environment(\.colorSchemeContrast) var contrast var textColor: Color { contrast == .increased ? .primary : .secondary } ``` ## Accessibility Checklist - [ ] All images have accessibility labels - [ ] Touch targets are minimum 44×44pt - [ ] Text scales with Dynamic Type - [ ] Color contrast meets WCAG standards - [ ] Motion can be reduced - [ ] VoiceOver navigation is logical - [ ] Don't rely solely on color to convey meaning -
app-icons.md 704 B
# iOS App Icons ## Required Sizes | Size | Usage | |------|-------| | 1024 × 1024px | App Store | | 180 × 180px | iPhone home screen (@3x) | | 120 × 120px | iPhone home screen (@2x), Spotlight | | 167 × 167px | iPad Pro | | 152 × 152px | iPad (@2x) | | 87 × 87px | Settings | ## Icon Shape iOS applies a **superellipse** ("squircle") mask automatically. Export icons as squares. For custom border matching the shape: - Corner radius = side length × 0.222 - Corner smoothing = 61% (iOS preset in Figma) ## Icon Guidelines - Simple, recognizable silhouette - Works at all sizes (remove fine details for small sizes) - Consider light, dark, and tinted variants (iOS 18+) - Avoid text in icons -
colors-depth.md 1.5 KB
# iOS Colors & Theming Reference SKILL.md section 3 covers the core semantic palette (`label`, `secondaryLabel`, `systemBackground`, `systemBlue`/`systemRed`/`systemGreen`) and the three Dark Mode adaptation rules. This file adds what it does not carry: the extra layering tokens and the full WCAG contrast table. ## Extra Semantic Tokens (beyond the core set) For multi-level hierarchy and grouped layouts, reach past the core six: ```swift Color(.tertiaryLabel) // 3rd-level text (placeholders, disabled) Color(.quaternaryLabel) // 4th-level (separators, faint glyphs) Color(.secondarySystemBackground) // elevated cards / grouped table sections Color(.tertiarySystemBackground) // a layer above secondary Color(.systemGroupedBackground) // base behind grouped lists (Settings style) Color(.separator) // hairline dividers (already mode-aware) ``` Use the grouped-background family for `.insetGrouped` lists; use the plain `systemBackground` family for full-bleed content. ## Color Contrast (full WCAG table) SKILL.md states the 4.5:1 floor for body text. The complete set of minimums: | Content | Minimum ratio | |---------|---------------| | Normal text (< 18pt, or < 14pt bold) | 4.5:1 | | Large text (>= 18pt, or >= 14pt bold) | 3:1 | | UI components and graphical objects (icons, control borders, focus rings) | 3:1 | Verify with Xcode's Accessibility Inspector color contrast check, in both light and dark modes and with Increase Contrast enabled. -
components.md 5.7 KB
# iOS UI Components ## Buttons **Page-level actions**: Appear in nav bar (top) or action bar (bottom) ``` ┌─────────────────────────────────┐ │ Cancel Save Edit │ ← Nav bar actions ├─────────────────────────────────┤ │ │ │ Page Content │ │ │ ├─────────────────────────────────┤ │ Share Copy Delete │ ← Action bar └─────────────────────────────────┘ ``` **On-page buttons**: Often appear on cards or sections - Primary buttons: Filled with theme color - Secondary buttons: Outlined or text-only - Destructive actions: Red text/color ## Lists (Table Views) Lists are fundamental to iOS design. Configure each row with: **Left side** (optional): - Icon or image **Center**: - Primary text (17pt regular) - Secondary text (15pt or 12pt, lighter color) - Tertiary text (if needed) **Right side** (choose one): - Chevron (→) — navigates to detail screen - Text + Chevron — shows current value, tappable to change - Checkmark (✓) — single selection from list - Switch — toggle on/off - Text button — action link ## Input Controls Most inputs are styled as list items: **Text Input**: ``` ┌─────────────────────────────────┐ │ Email │ ← Hint text disappears on typing └─────────────────────────────────┘ ``` **Switch**: ``` ┌─────────────────────────────────┐ │ Notifications [====○] │ └─────────────────────────────────┘ ``` **Date/Time Picker**: ``` ┌─────────────────────────────────┐ │ Date [ Jan 15, 2025 ] │ ← Light gray button, expands inline └─────────────────────────────────┘ ``` **Picker Screen Pattern**: - List item shows current value + chevron - Tapping navigates to selection screen - Selected option marked with checkmark ## Pull-Down Menus For short option lists without navigation: ```swift Menu("Options") { Button("Edit", action: edit) Button("Share", action: share) Divider() Button("Delete", role: .destructive, action: delete) } ``` ## Touch Targets & Spacing ### Minimum Touch Target **44 × 44 points** — This is non-negotiable for all interactive elements. ```swift Button("Tap") { // Action } .frame(minWidth: 44, minHeight: 44) ``` ### Standard Spacing Values | Spacing | Usage | |---------|-------| | 8pt | Tight spacing (related elements) | | 16pt | Standard spacing (sections) | | 20pt | Screen edge margins | | 24pt | Loose spacing (major sections) | ```swift VStack(spacing: 16) { // Standard component spacing } ``` --- ## Menu Patterns ### Context Menus Long-press to reveal contextual actions: ```swift Text("Item") .contextMenu { Button("Edit", action: edit) Button("Share", action: share) Divider() Button("Delete", role: .destructive, action: delete) } ``` **Guidelines:** - Group related actions - Use dividers between groups - Destructive actions at bottom, marked red - Include icons where helpful ### Pull-Down Menus Tap to reveal options without navigation: ```swift Menu("Options") { Button("Sort by Name", action: sortByName) Button("Sort by Date", action: sortByDate) Divider() Menu("Filter") { Button("Active", action: filterActive) Button("Completed", action: filterCompleted) } } ``` **When to use:** - 3-6 options that don't need full screen - Actions that don't require additional input - Sorting, filtering, view options ### Action Sheets For important decisions requiring attention: ```swift .confirmationDialog("Choose Action", isPresented: $showingSheet) { Button("Camera") { } Button("Photo Library") { } Button("Cancel", role: .cancel) { } } ``` **Guidelines:** - Title is optional (use for clarity) - Limit to 5-6 actions - Cancel is always last - Destructive actions in red --- ## Confirmation Dialogs ### When to Confirm | Action | Needs Confirmation? | |--------|---------------------| | Delete single item | Sometimes (if permanent) | | Delete multiple items | Yes | | Discard unsaved changes | Yes | | Log out | Usually not | | Send message | No | | Purchase | Yes (final step) | ### Alert Structure ```swift .alert("Delete Item?", isPresented: $showingAlert) { Button("Delete", role: .destructive, action: deleteItem) Button("Cancel", role: .cancel) { } } message: { Text("This action cannot be undone.") } ``` **Guidelines:** - Title: Clear action question - Message: Explain consequences (brief) - Buttons: Specific verbs, not just "OK" - Destructive button on left (less expected position) - Cancel on right (easy to tap) ### Undo vs. Confirm **Prefer undo when:** - Action is recoverable - Speed matters - Confirmations would be annoying **Require confirmation when:** - Action is irreversible - Data/money loss possible - Action affects others ```swift // Undo pattern .toolbar { Button("Undo") { undoManager.undo() } } // Swipe to delete with undo toast .swipeActions(edge: .trailing) { Button("Delete", role: .destructive) { withAnimation { deleteWithUndo(item) } } } ``` -
gestures.md 1.2 KB
# iOS Gestures & Interactions ## Standard Gestures (Never Override) | Gesture | Standard Action | |---------|-----------------| | Swipe right from left edge | Navigate back | | Swipe down on modal | Dismiss modal | | Pull down on list | Refresh content | | Swipe left on row | Reveal actions (delete, etc.) | | Pinch | Zoom in/out | | Long press | Context menu | ## Haptic Feedback Provide tactile feedback for meaningful interactions: ```swift // Impact feedback (physical actions) let impact = UIImpactFeedbackGenerator(style: .medium) impact.impactOccurred() // Notification feedback (outcomes) let notification = UINotificationFeedbackGenerator() notification.notificationOccurred(.success) // or .warning, .error // Selection feedback (UI changes) let selection = UISelectionFeedbackGenerator() selection.selectionChanged() ``` ## Animation Guidelines - Use spring animations for natural, bouncy feel - Respect `reduceMotion` accessibility setting - Keep animations brief and purposeful ```swift @Environment(\.accessibilityReduceMotion) var reduceMotion var animation: Animation { reduceMotion ? .none : .spring() } withAnimation(animation) { // Animate property changes } ``` -
keyboard-input.md 11.5 KB
# iOS Keyboard & Input Patterns Comprehensive guide to keyboard handling, text input, and hardware keyboard support. ## Table of Contents 1. [Software Keyboard Types](#software-keyboard-types) 2. [Text Content Types](#text-content-types) 3. [Input Accessory Views](#input-accessory-views) 4. [Keyboard Avoidance](#keyboard-avoidance) 5. [Hardware Keyboard Support](#hardware-keyboard-support) 6. [Text Editing](#text-editing) 7. [Secure Text Entry](#secure-text-entry) 8. [Search Input](#search-input) 9. [Common Patterns](#common-patterns) --- ## Software Keyboard Types ### Choosing the Right Keyboard Match the keyboard to the expected input: | Input Type | Keyboard | UIKeyboardType | |------------|----------|----------------| | General text | Default | `.default` | | Email | Email-optimized (@ and . prominent) | `.emailAddress` | | URL | URL-optimized (/, .com) | `.URL` | | Phone number | Number pad | `.phonePad` | | Numeric (with punctuation) | Numbers + punctuation | `.numbersAndPunctuation` | | Numeric only | Decimal pad | `.decimalPad` | | Twitter handle | Twitter keyboard | `.twitter` | | Web search | Search keyboard | `.webSearch` | | ASCII only | ASCII-capable | `.asciiCapable` | ### SwiftUI Implementation ```swift TextField("Email", text: $email) .keyboardType(.emailAddress) .textContentType(.emailAddress) .autocapitalization(.none) .disableAutocorrection(true) ``` ### UIKit Implementation ```swift textField.keyboardType = .emailAddress textField.textContentType = .emailAddress textField.autocapitalizationType = .none textField.autocorrectionType = .no ``` --- ## Text Content Types Enable autofill by specifying content types: | Content | textContentType | Enables | |---------|-----------------|---------| | Name | `.name` | Contact autofill | | Given name | `.givenName` | | | Family name | `.familyName` | | | Email | `.emailAddress` | Email autofill | | Phone | `.telephoneNumber` | Phone autofill | | Address | `.streetAddressLine1` | Address autofill | | City | `.addressCity` | | | State | `.addressState` | | | ZIP | `.postalCode` | | | Country | `.countryName` | | | Credit card | `.creditCardNumber` | Camera card scanning | | Username | `.username` | Keychain autofill | | Password | `.password` | Keychain autofill | | New password | `.newPassword` | Password generation | | One-time code | `.oneTimeCode` | SMS autofill | ### Password and Login Fields ```swift // Login form TextField("Email", text: $email) .textContentType(.username) .keyboardType(.emailAddress) SecureField("Password", text: $password) .textContentType(.password) // Registration form SecureField("Create Password", text: $newPassword) .textContentType(.newPassword) ``` ### One-Time Code (2FA) ```swift TextField("Verification Code", text: $code) .textContentType(.oneTimeCode) .keyboardType(.numberPad) ``` iOS automatically suggests codes from SMS messages when this content type is set. --- ## Input Accessory Views ### When to Use Input accessory views appear above the keyboard for: - Navigation between fields (Previous/Next) - Custom actions (Done, formatting buttons) - Context-specific tools ### Standard Toolbar Pattern ```swift struct FormTextField: View { @Binding var text: String @FocusState private var isFocused: Bool var body: some View { TextField("Value", text: $text) .focused($isFocused) .toolbar { ToolbarItemGroup(placement: .keyboard) { Spacer() Button("Done") { isFocused = false } } } } } ``` ### Multi-Field Navigation ```swift struct FormView: View { @FocusState private var focusedField: Field? enum Field { case firstName, lastName, email } var body: some View { Form { TextField("First Name", text: $firstName) .focused($focusedField, equals: .firstName) TextField("Last Name", text: $lastName) .focused($focusedField, equals: .lastName) TextField("Email", text: $email) .focused($focusedField, equals: .email) } .toolbar { ToolbarItemGroup(placement: .keyboard) { Button(action: focusPrevious) { Image(systemName: "chevron.up") } .disabled(!canFocusPrevious) Button(action: focusNext) { Image(systemName: "chevron.down") } .disabled(!canFocusNext) Spacer() Button("Done") { focusedField = nil } } } } } ``` --- ## Keyboard Avoidance ### Automatic Behavior (SwiftUI) SwiftUI automatically adjusts for keyboard in most cases: - ScrollViews scroll to keep focused field visible - Safe area adjusts for keyboard height ### Manual Keyboard Handling ```swift struct KeyboardAdaptive: ViewModifier { @State private var keyboardHeight: CGFloat = 0 func body(content: Content) -> some View { content .padding(.bottom, keyboardHeight) .onReceive(Publishers.keyboardHeight) { height in withAnimation(.easeOut(duration: 0.25)) { keyboardHeight = height } } } } ``` ### Best Practices 1. **Scroll to show field** - Focused field should be visible 2. **Don't cover important actions** - Submit buttons should remain accessible 3. **Animate adjustments** - Match keyboard animation (0.25s ease-out) 4. **Test on different devices** - Keyboard heights vary --- ## Hardware Keyboard Support ### Why It Matters iPads commonly use hardware keyboards. Apps should: - Support standard keyboard shortcuts - Provide discoverability - Not break when hardware keyboard is attached ### Standard Shortcuts to Support | Shortcut | Action | Notes | |----------|--------|-------| | ⌘N | New item | Standard creation | | ⌘S | Save | | | ⌘⌫ | Delete | With confirmation | | ⌘F | Find/Search | | | ⌘Z | Undo | | | ⌘⇧Z | Redo | | | ⌘C/⌘V/⌘X | Copy/Paste/Cut | | | ⌘, | Settings/Preferences | | | ⌘W | Close window/modal | | | Escape | Cancel/dismiss | | | Tab | Next field | | | ⇧Tab | Previous field | | | Return | Submit form | When appropriate | ### SwiftUI Keyboard Shortcuts ```swift struct ContentView: View { var body: some View { NavigationStack { ItemList() .toolbar { Button("New Item", action: createItem) .keyboardShortcut("n", modifiers: .command) } } } } // For custom shortcuts in list items List { ForEach(items) { item in ItemRow(item: item) } } .onDeleteCommand(perform: deleteSelected) // ⌘⌫ ``` ### Keyboard Shortcut Discoverability Hold ⌘ to show available shortcuts. Ensure your shortcuts appear: ```swift Button("Save", action: save) .keyboardShortcut("s", modifiers: .command) // Appears in keyboard shortcut overlay ``` --- ## Text Editing ### Autocapitalization | Style | Use For | |-------|---------| | `.sentences` | General text, messages | | `.words` | Names, titles | | `.allCharacters` | Codes, abbreviations | | `.none` | Email, usernames, URLs | ### Autocorrection | Setting | Use For | |---------|---------| | Enabled (default) | Prose, messages | | Disabled | Code, usernames, specific values | ```swift TextField("Username", text: $username) .autocapitalization(.none) .disableAutocorrection(true) ``` ### Text Input Traits Summary ```swift TextField("Email", text: $email) .keyboardType(.emailAddress) // Keyboard layout .textContentType(.emailAddress) // Autofill hint .autocapitalization(.none) // No auto-caps .disableAutocorrection(true) // No autocorrect .textInputAutocapitalization(.never) // iOS 15+ ``` --- ## Secure Text Entry ### Password Fields ```swift SecureField("Password", text: $password) .textContentType(.password) ``` ### Show/Hide Toggle Pattern ```swift struct PasswordField: View { @Binding var password: String @State private var isSecure = true var body: some View { HStack { if isSecure { SecureField("Password", text: $password) } else { TextField("Password", text: $password) } Button(action: { isSecure.toggle() }) { Image(systemName: isSecure ? "eye.slash" : "eye") .foregroundColor(.secondary) } } .textContentType(.password) } } ``` --- ## Search Input ### Search Field Behavior ```swift struct SearchView: View { @State private var searchText = "" var body: some View { NavigationStack { List(filteredItems) { item in ItemRow(item: item) } .searchable( text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search items" ) } } } ``` ### Search Suggestions ```swift .searchable(text: $searchText) { ForEach(suggestions) { suggestion in Text(suggestion.name) .searchCompletion(suggestion.name) } } ``` --- ## Common Patterns ### Form with All Best Practices ```swift struct RegistrationForm: View { @State private var email = "" @State private var password = "" @State private var confirmPassword = "" @FocusState private var focusedField: Field? enum Field: Hashable { case email, password, confirmPassword } var body: some View { Form { Section("Account") { TextField("Email", text: $email) .keyboardType(.emailAddress) .textContentType(.username) .autocapitalization(.none) .disableAutocorrection(true) .focused($focusedField, equals: .email) .submitLabel(.next) .onSubmit { focusedField = .password } SecureField("Password", text: $password) .textContentType(.newPassword) .focused($focusedField, equals: .password) .submitLabel(.next) .onSubmit { focusedField = .confirmPassword } SecureField("Confirm Password", text: $confirmPassword) .textContentType(.newPassword) .focused($focusedField, equals: .confirmPassword) .submitLabel(.done) .onSubmit(register) } Section { Button("Create Account", action: register) } } .toolbar { ToolbarItemGroup(placement: .keyboard) { Button("Previous") { moveFocus(-.previous) } Button("Next") { moveFocus(.next) } Spacer() Button("Done") { focusedField = nil } } } } } ``` ### Submit Label Options | Label | Use For | |-------|---------| | `.done` | Final field, closes keyboard | | `.go` | Triggers action (search, navigate) | | `.next` | Moves to next field | | `.return` | Inserts newline (text areas) | | `.search` | Search field | | `.send` | Message composition | | `.continue` | Multi-step forms | | `.join` | Joining/connecting | | `.route` | Navigation apps | ```swift TextField("Search", text: $query) .submitLabel(.search) .onSubmit { performSearch() } ``` -
navigation.md 4.5 KB
# iOS Navigation Patterns ## Tab Bar (Primary Navigation) The tab bar provides access to main app destinations. - **Position**: Bottom of screen, always visible (except modals/keyboards) - **Items**: 2-5 tabs maximum - **Overflow**: Use "More" tab if >5 destinations needed - **Selected state**: Fill color indicates active tab - **Labels**: 10pt SF text - **Background**: Slightly translucent with background blur ("frosted glass") **Behavior**: - Each tab remembers its navigation state - Tapping active tab returns to root screen of that tab - Tab bar hidden during modals and keyboard display ## Navigation Bar (Contextual Navigation) - **Back button**: Top-left, allows return to previous screen - **Actions**: Top-right, context-specific actions - **Title**: Center (scrolled state) or left-aligned large title (unscrolled) **Scroll Behavior**: - Large title collapses to compact centered title on scroll - Search bar can move or hide on scroll - Smooth animated transitions between states ## Navigating Back | Method | Context | |--------|---------| | "Back" button (top-left) | Standard navigation | | Swipe right from left edge | Standard navigation | | "Cancel" / "Done" button | Modal views | | Swipe down on content | Modals, fullscreen media | ## Modal Sheets Use modals for focused tasks that shouldn't interrupt context completely. - Slides up from bottom - Previous screen visible (recessed) in background - Dismiss via: close button, swipe down, or completing task --- ## Search UI Patterns ### Search Bar Placement | Context | Placement | |---------|-----------| | Primary search (core feature) | Navigation bar, persistent | | Secondary search | Below nav, hidden on scroll | | List filtering | Above list, inline | ### Search Behavior **States:** 1. **Inactive:** Placeholder text, magnifying glass icon 2. **Active/Focused:** Keyboard appears, cancel button shows 3. **Typing:** Results update (instant or debounced) 4. **Results:** Displayed in list below **SwiftUI implementation:** ```swift .searchable( text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search items" ) ``` ### Search Suggestions - Recent searches - Trending/popular searches - Autocomplete suggestions - Scoped suggestions (filter by category) ```swift .searchable(text: $searchText) { ForEach(suggestions) { suggestion in Text(suggestion.name) .searchCompletion(suggestion.name) } } ``` --- ## Split View Navigation (iPad) ### Two-Column Layout ``` ┌──────────────────┬────────────────────────────────┐ │ │ │ │ Primary List │ Detail View │ │ (Sidebar) │ │ │ │ │ │ Item 1 │ Selected item details │ │ Item 2 ← │ │ │ Item 3 │ │ │ │ │ └──────────────────┴────────────────────────────────┘ ``` **Behavior:** - Primary column: 320pt default width - Detail column: Fills remaining space - Collapse to single column on compact width ### Three-Column Layout ``` ┌────────────┬────────────┬──────────────────────────┐ │ Sidebar │ Content │ Detail │ │ │ │ │ │ Section 1 │ Item A │ Item details here │ │ Section 2 │ Item B ← │ │ │ Section 3 │ Item C │ │ └────────────┴────────────┴──────────────────────────┘ ``` **SwiftUI:** ```swift NavigationSplitView { Sidebar() } content: { ContentList() } detail: { DetailView() } ``` ### Responsive Behavior | Width | Behavior | |-------|----------| | Compact (iPhone) | Stack navigation | | Regular (iPad portrait) | Two-column or overlay | | Regular (iPad landscape) | Three-column available | **Best practices:** - Show placeholder in detail when nothing selected - Remember selection across rotation - Support column resizing (where appropriate) -
privacy-permissions.md 13.5 KB
# iOS Privacy & Permissions Best practices for permission requests, privacy UI, and building user trust. ## Table of Contents 1. [Permission Request Philosophy](#permission-request-philosophy) 2. [Permission Request Timing](#permission-request-timing) 3. [Permission Types & Best Practices](#permission-types-best-practices) 4. [Handling Denied Permissions](#handling-denied-permissions) 5. [Privacy UI Patterns](#privacy-ui-patterns) 6. [App Privacy Labels](#app-privacy-labels) 7. [Usage String Best Practices](#usage-string-best-practices) 8. [Testing Permissions](#testing-permissions) --- ## Permission Request Philosophy **Core principle:** Request permissions only when needed, explain why, and respect "no." Users are increasingly permission-fatigued. Every unnecessary or poorly-timed request damages trust and increases denial rates. --- ## Permission Request Timing ### Just-In-Time Requests Request permissions when the user takes an action that requires them, not at app launch. **Bad:** Request camera permission on first launch **Good:** Request camera permission when user taps "Take Photo" ### The Pre-Permission Pattern Before the system dialog, show a custom screen explaining the value. ``` ┌─────────────────────────────────────────┐ │ │ │ [Camera icon] │ │ │ │ Take photos of your receipts │ │ │ │ We use your camera to quickly │ │ scan and organize your expenses. │ │ Photos are stored only on your │ │ device. │ │ │ │ ┌─────────────────────────────────┐ │ │ │ Allow Camera │ │ │ └─────────────────────────────────┘ │ │ │ │ Maybe Later │ │ │ └─────────────────────────────────────────┘ ``` **Benefits:** - Explains value before system dialog - "Maybe Later" doesn't trigger system denial - Higher acceptance rates - Better user understanding ### System Permission Dialog ```swift // Camera AVCaptureDevice.requestAccess(for: .video) { granted in // Handle response } // Photos PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in // Handle status } // Location locationManager.requestWhenInUseAuthorization() // or locationManager.requestAlwaysAuthorization() // Notifications UNUserNotificationCenter.current().requestAuthorization( options: [.alert, .badge, .sound] ) { granted, error in // Handle response } ``` --- ## Permission Types & Best Practices ### Camera **When to request:** When user initiates camera action **Usage string example:** "[App] needs camera access to scan documents and take photos for your projects." **Best practices:** - Only request when camera feature is used - Offer photo library as alternative - Handle denial gracefully (show library option) ### Photo Library **Access levels (iOS 14+):** - `.addOnly` - Can add photos, can't read (for saving) - `.readWrite` - Full access - Limited selection - User picks specific photos **When to request:** When user wants to access photos **Usage string example:** "[App] accesses your photos to let you add images to your posts." **Best practices:** - Request `.addOnly` if you only need to save - Support limited photo selection (don't require full access) - Use PHPicker for one-time selection (no permission needed) ```swift // PHPicker - no permission required var config = PHPickerConfiguration() config.selectionLimit = 1 config.filter = .images let picker = PHPickerViewController(configuration: config) ``` ### Location **Authorization levels:** - `.whenInUse` - Only while app is active - `.always` - Background location access **When to request:** When location feature is needed **Usage strings needed:** - `NSLocationWhenInUseUsageDescription` - `NSLocationAlwaysAndWhenInUseUsageDescription` (for always) **Best practices:** - Start with "When In Use" before requesting "Always" - Explain why background location is needed - Provide value even without location - Use significant location changes if precise tracking unnecessary ```swift // Request when in use first locationManager.requestWhenInUseAuthorization() // Later, if needed, escalate to always // (triggers new system prompt explaining upgrade) locationManager.requestAlwaysAuthorization() ``` ### Notifications **When to request:** After user has experienced app value **Usage string example:** "[App] sends notifications for messages from your team and important updates." **Best practices:** - Don't request on first launch - Wait until user has seen value - Explain what notifications they'll receive - Provide in-app notification preferences - Respect system settings **Provisional notifications (iOS 12+):** ```swift // Quietly delivered to Notification Center // User can choose to keep or turn off UNUserNotificationCenter.current().requestAuthorization( options: [.provisional, .alert, .sound] ) { granted, error in } ``` ### Contacts **When to request:** When user initiates contact-related feature **Usage string example:** "[App] accesses your contacts to help you invite friends and find people you know." **Best practices:** - Use CNContactPickerViewController when possible (no permission) - Only request full access when truly needed - Never sync contacts without explicit permission ### Microphone **When to request:** When user initiates audio recording **Usage string example:** "[App] uses your microphone to record voice messages and audio notes." **Best practices:** - Clear indicator when recording - Option to preview before sending - Explain storage/transmission of audio ### Health Data **When to request:** When user enables health features **Best practices:** - Request only specific data types needed - Explain how data will be used - Provide value without health access - Handle partial authorization ### Tracking (ATT) **When to request:** Before tracking user across apps **Required prompt:** ```swift ATTrackingManager.requestTrackingAuthorization { status in switch status { case .authorized: // Enable tracking case .denied, .restricted: // Disable tracking case .notDetermined: // Request hasn't been shown yet } } ``` **Best practices:** - Explain value of personalized ads first - Don't punish users who decline - App must function without tracking --- ## Handling Denied Permissions ### Graceful Degradation Always provide alternative paths when permission is denied. | Permission Denied | Alternative | |-------------------|-------------| | Camera | Photo library option | | Location | Manual address entry | | Notifications | In-app message center | | Contacts | Manual contact entry | | Photos | Camera-only option | ### Re-Requesting After Denial Once denied, system won't show prompt again. Guide users to Settings. ```swift func openAppSettings() { guard let settingsUrl = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(settingsUrl) else { return } UIApplication.shared.open(settingsUrl) } ``` **UI pattern:** ``` ┌─────────────────────────────────────────┐ │ │ │ Camera Access Needed │ │ │ │ To scan documents, allow camera │ │ access in Settings. │ │ │ │ ┌─────────────────────────────────┐ │ │ │ Open Settings │ │ │ └─────────────────────────────────┘ │ │ │ │ Not Now │ │ │ └─────────────────────────────────────────┘ ``` ### Don't Ask Again (Within Session) If user taps "Maybe Later" on pre-permission screen, don't immediately ask again. ```swift // Track dismissal UserDefaults.standard.set(Date(), forKey: "camera_prompt_dismissed") // Wait before showing again func shouldShowCameraPrompt() -> Bool { guard let lastDismissed = UserDefaults.standard.object( forKey: "camera_prompt_dismissed" ) as? Date else { return true } // Wait at least 3 days return Date().timeIntervalSince(lastDismissed) > 3 * 24 * 60 * 60 } ``` --- ## Privacy UI Patterns ### Permission Status Indicators Show current permission state in settings: ``` ┌─────────────────────────────────────────┐ │ Permissions │ ├─────────────────────────────────────────┤ │ 📷 Camera Allowed ▶ │ │ 📍 Location While Using▶ │ │ 🔔 Notifications Off ▶ │ │ 📱 Contacts Not Asked ▶ │ └─────────────────────────────────────────┘ ``` ### Data Usage Transparency Explain what data is collected and why: ``` ┌─────────────────────────────────────────┐ │ Privacy │ ├─────────────────────────────────────────┤ │ │ │ Data We Collect │ │ │ │ • Usage analytics │ │ To improve app performance │ │ │ │ • Crash reports │ │ To fix bugs and issues │ │ │ │ • Photos you upload │ │ Stored securely on our servers │ │ │ │ [View Privacy Policy] │ │ │ └─────────────────────────────────────────┘ ``` ### Data Deletion Options Provide clear data management: ``` ┌─────────────────────────────────────────┐ │ Your Data │ ├─────────────────────────────────────────┤ │ Download My Data ▶ │ │ Delete My Account ▶ │ └─────────────────────────────────────────┘ ``` --- ## App Privacy Labels ### Required Categories Your App Store listing must declare: **Data Used to Track You** - Data used for advertising across apps **Data Linked to You** - Identifiable data (name, email, etc.) **Data Not Linked to You** - Anonymous analytics, crash data ### Best Practices - Be accurate—Apple verifies - Minimize collection to reduce label size - Simpler labels build trust - Update when collection changes --- ## Usage String Best Practices ### Structure ``` "[App name] [action] to [user benefit]." ``` ### Examples by Permission | Permission | Good Example | |------------|--------------| | Camera | "MyApp uses the camera to scan barcodes for quick product lookup." | | Photos | "MyApp saves photos you create to your photo library." | | Location | "MyApp uses your location to show nearby restaurants and estimated delivery times." | | Microphone | "MyApp uses the microphone to record voice notes for your journal entries." | | Contacts | "MyApp accesses contacts to help you split bills with friends." | ### What to Avoid - Generic explanations ("to improve your experience") - Technical jargon - Mentioning advertising without explaining value - Being vague about data use --- ## Testing Permissions ### Reset Permissions ```bash # Reset all permissions for specific app xcrun simctl privacy booted reset all com.yourapp.bundleid # Reset specific permission xcrun simctl privacy booted reset camera com.yourapp.bundleid ``` ### Test All States For each permission: 1. Never requested (first launch) 2. Authorized 3. Denied 4. Restricted (parental controls) 5. Limited (Photos) 6. Provisional (Notifications) ### Automated Testing ```swift func testCameraPermissionDenied() { // Set up mock authorization status mockCameraAuthorization = .denied // Trigger camera feature app.buttons["Take Photo"].tap() // Verify fallback UI appears XCTAssertTrue(app.staticTexts["Camera access needed"].exists) XCTAssertTrue(app.buttons["Open Settings"].exists) } ``` -
system-integration.md 9.3 KB
# iOS System Integration Siri, Shortcuts, Handoff, drag and drop, and other system-level integrations. ## Table of Contents 1. [Siri Integration](#siri-integration) 2. [Shortcuts App Integration](#shortcuts-app-integration) 3. [Handoff](#handoff) 4. [Drag and Drop](#drag-and-drop) 5. [Universal Links](#universal-links) 6. [Spotlight Search](#spotlight-search) 7. [Focus & Notifications](#focus-notifications) 8. [Quick Note Integration](#quick-note-integration) 9. [SharePlay](#shareplay) 10. [System Appearance](#system-appearance) 11. [Best Practices Summary](#best-practices-summary) --- ## Siri Integration ### SiriKit Domains Your app can integrate with Siri through predefined domains: | Domain | Example Intents | |--------|-----------------| | Messaging | Send message, search messages | | Lists & Notes | Create note, add to list | | Payments | Send payment, request payment | | Workouts | Start workout, end workout | | Media | Play media, add to library | | Ride booking | Request ride, get ride status | | Car commands | Lock car, get car status | | Visual codes | Look up barcode, QR code | ### Designing for Voice **Confirmation dialogs:** ``` Siri: "Send $50 to Sarah for dinner?" User: "Yes" / "Change amount" / "Cancel" ``` **Guidelines:** - Confirm significant actions - Allow easy correction - Provide visual feedback alongside voice - Handle ambiguity gracefully ### Custom Intents For actions not in predefined domains: ```swift // Define in Intents.intentdefinition Intent: OrderCoffee Parameters: coffeeType, size, location ``` **Best practices:** - Use descriptive parameter names - Provide good examples - Support synonyms - Test with various phrasings ### Siri Shortcuts Allow users to create custom voice triggers: ```swift // Donate shortcut when user completes action let activity = NSUserActivity(activityType: "com.app.order-favorite") activity.title = "Order my usual coffee" activity.isEligibleForSearch = true activity.isEligibleForPrediction = true activity.suggestedInvocationPhrase = "Order my usual" view.userActivity = activity ``` **Guidelines:** - Donate shortcuts for repeated actions - Suggest clear invocation phrases - Provide relevant parameters - Test in Shortcuts app --- ## Shortcuts App Integration ### App Shortcuts (iOS 16+) Pre-built shortcuts that appear automatically: ```swift struct MyAppShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: OrderCoffeeIntent(), phrases: [ "Order coffee with \(.applicationName)", "Get my usual from \(.applicationName)" ], shortTitle: "Order Coffee", systemImageName: "cup.and.saucer.fill" ) } } ``` ### Shortcut Actions Expose app functionality as Shortcuts actions: **Good candidates:** - Actions users repeat frequently - Actions that can run without UI - Data that can be passed to other apps - Automatable workflows **Design considerations:** - Clear action names (verb + object) - Meaningful parameters with defaults - Useful outputs for chaining - Error messages that explain what went wrong --- ## Handoff ### Enabling Handoff Allow users to continue activities across Apple devices: ```swift let activity = NSUserActivity(activityType: "com.app.viewing-item") activity.title = "Viewing Product: \(product.name)" activity.userInfo = ["productID": product.id] activity.isEligibleForHandoff = true activity.webpageURL = URL(string: "https://myapp.com/product/\(product.id)") userActivity = activity ``` ### Handoff Guidelines **Do:** - Continue at exactly where user left off - Restore scroll position, form state, etc. - Support universal links as fallback - Update activity as context changes **Don't:** - Require re-authentication - Lose user's work - Show significantly different content ### Web Fallback If app isn't installed on receiving device: ```swift activity.webpageURL = URL(string: "https://myapp.com/activity/\(id)") ``` --- ## Drag and Drop ### Supporting Drag ```swift .draggable(item) { // Drag preview ItemPreview(item: item) } ``` ### Supporting Drop ```swift .dropDestination(for: ItemType.self) { items, location in // Handle dropped items return true } ``` ### Drag and Drop Guidelines **Visual feedback:** - Show clear drag preview - Indicate valid drop targets - Animate transitions smoothly **Multi-item:** - Support selecting multiple items - Stack preview for multiple items - Handle batch operations **Cross-app:** - Export standard data types (images, text, URLs) - Accept common formats - Maintain quality during transfer ### Platform Considerations | Platform | Drag Initiation | |----------|-----------------| | iPhone | Long press + drag (within app) | | iPad | Long press or tap + drag | | Mac (Catalyst) | Click + drag | --- ## Universal Links ### Setting Up 1. Configure `apple-app-site-association` on your server: ```json { "applinks": { "apps": [], "details": [{ "appID": "TEAMID.com.example.app", "paths": ["/product/*", "/user/*"] }] } } ``` 2. Add Associated Domains capability: ``` applinks:example.com ``` ### Handling Links ```swift func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return false } return handleUniversalLink(url) } ``` ### Best Practices - Parse URLs robustly (handle malformed links) - Navigate to appropriate screen - Show content immediately (don't require login first) - Fall back gracefully if content unavailable --- ## Spotlight Search ### Indexing Content ```swift let attributeSet = CSSearchableItemAttributeSet(contentType: .text) attributeSet.title = item.title attributeSet.contentDescription = item.description attributeSet.thumbnailData = item.thumbnailData let searchableItem = CSSearchableItem( uniqueIdentifier: item.id, domainIdentifier: "com.app.items", attributeSet: attributeSet ) CSSearchableIndex.default().indexSearchableItems([searchableItem]) ``` ### What to Index **Good candidates:** - User content (notes, documents) - Saved items (favorites, history) - Frequently accessed items **Avoid:** - Sensitive data - Transient content - Every possible item (be selective) ### Search Result Design Results appear in Spotlight: ``` ┌─────────────────────────────────────────┐ │ 🔲 My Note Title │ │ Preview of note content... │ │ MyApp │ └─────────────────────────────────────────┘ ``` **Include:** - Clear title - Helpful description - Thumbnail if visual - Accurate metadata --- ## Focus & Notifications ### Focus Awareness Respect user's Focus mode: ```swift UNUserNotificationCenter.current().getNotificationSettings { settings in if settings.notificationCenterSetting == .disabled { // User has notifications silenced } } ``` ### Time Sensitive Notifications For truly urgent notifications: ```swift let content = UNMutableNotificationContent() content.title = "Your ride is here" content.interruptionLevel = .timeSensitive ``` **Use only when:** - Immediate action required - User explicitly opted in - Content is genuinely time-sensitive --- ## Quick Note Integration ### Adding Quick Note Capability Allow highlighting content for Quick Note: ```swift Text(content) .contextMenu { Button("Add to Quick Note") { // System handles this } } ``` --- ## SharePlay ### When to Use SharePlay - Watching content together - Collaborative activities - Shared experiences ### SharePlay Guidelines **Sync state:** - Keep all participants in sync - Handle network latency gracefully - Provide individual controls where appropriate **Visual design:** - Show who's in the session - Indicate when others interact - Provide easy leave option --- ## System Appearance ### Supporting Dark Mode ```swift // Adaptive colors Color.primary // Auto light/dark Color.secondary // Auto light/dark // Custom adaptive colors extension Color { static let background = Color("Background") // From asset catalog } ``` ### Supporting Dynamic Type ```swift Text("Title") .font(.title) // Scales with Dynamic Type // Custom scalable fonts .font(.custom("MyFont", size: 17, relativeTo: .body)) ``` ### Supporting Accessibility ```swift Text("Content") .accessibilityLabel("Detailed description") .accessibilityHint("Tap to view details") ``` --- ## Best Practices Summary | Integration | Key Consideration | |-------------|-------------------| | Siri | Clear confirmation, handle ambiguity | | Shortcuts | Expose repeatable, automatable actions | | Handoff | Preserve exact state across devices | | Drag & Drop | Clear previews, multi-item support | | Universal Links | Deep link to specific content | | Spotlight | Index valuable, non-sensitive content | | Focus | Respect user's notification preferences | **Universal principle:** System integrations should feel seamless—users shouldn't think about which device or app they're using. -
typography.md 1.7 KB
# iOS Typography Reference SKILL.md section 2 covers the sizes, weights, and the six type rules (min 11pt, 1.3x line height, 35-50 chars, left-aligned, weight-over-size, 4.5:1). This file adds what it does not carry: exact light-mode color values and the Dark Mode color mapping. ## System Font: San Francisco iOS uses San Francisco (SF Pro) as the default typeface. Always reach the styles through semantic APIs (`.font(.title)`, `.font(.body)`, `.font(.caption)`) so Dynamic Type scaling and these colors apply automatically — never hardcode the point sizes or hex values below. ## Light-Mode Color Values The point sizes live in SKILL.md; these are the hex values iOS resolves each semantic style to in light mode. | Element | Semantic style | Color (light) | |---------|----------------|---------------| | Large Title / Title / Body | `.largeTitle` `.title` `.body` | `#000000` (label) | | Secondary text | `.subheadline` + `.secondary` | `#3C3C43` @ 60% (secondaryLabel) | | Caption / Tertiary | `.caption` + `.secondary` | `#3C3C43` @ 60% | | Tab bar labels (unselected) | 10pt | `#8A8A8E` (tertiaryLabel) | ```swift Text("Caption") .font(.caption) .foregroundColor(.secondary) // resolves to secondaryLabel in both modes ``` ## Dark Mode Color Mapping Semantic styles flip automatically; this is the mapping they apply so you can verify a custom color matches it: - Primary text `#000000` -> `#FFFFFF` (label) - Secondary/tertiary gray -> lighter gray at the same opacity (secondaryLabel/tertiaryLabel) - Backgrounds shift darker while preserving the relative hierarchy between layers If you define a custom text color, put light/dark variants in the Asset Catalog so it follows this mapping instead of staying fixed. -
widgets-extensions.md 13.5 KB
# iOS Widgets & App Extensions Design guidelines for widgets, App Clips, and system extensions. ## Table of Contents 1. [Widget Design](#widget-design) 2. [Widget Configuration](#widget-configuration) 3. [App Clips](#app-clips) 4. [Share Extensions](#share-extensions) 5. [Action Extensions](#action-extensions) 6. [Live Activities](#live-activities) 7. [Widget Development Tips](#widget-development-tips) --- ## Widget Design ### Widget Philosophy Widgets provide **glanceable information** on the Home Screen, Lock Screen, and StandBy mode. They are not mini-apps—they're windows into your app's most useful content. **Key principles:** - Show immediately useful information - Update content thoughtfully (not constantly) - Respect the user's Home Screen aesthetic - Drive users to the app for deeper engagement ### Widget Sizes **Home Screen widgets:** | Size | Name | Grid Units | Use Case | |------|------|------------|----------| | Small | `systemSmall` | 2×2 | Single piece of information | | Medium | `systemMedium` | 4×2 | Key content + one interaction | | Large | `systemLarge` | 4×4 | Rich content, multiple items | | Extra Large | `systemExtraLarge` | 8×4 | iPad only, dashboard view | **Lock Screen widgets (iOS 16+):** | Size | Name | Characteristics | |------|------|-----------------| | Circular | `accessoryCircular` | Small icon or gauge | | Rectangular | `accessoryRectangular` | Text + small visual | | Inline | `accessoryInline` | Text only, above time | ### Widget Content Guidelines **Do:** - Show the most important information - Update content at meaningful intervals - Use the app's visual style - Support multiple sizes (let users choose) - Provide multiple widget types if you have different use cases **Don't:** - Cram too much information - Show stale data - Use widgets for advertising - Require interaction to see content - Update too frequently (drains battery) ### Small Widget Design ``` ┌─────────────────────┐ │ │ │ [Icon/Image] │ │ │ │ Primary Info │ │ Secondary │ │ │ └─────────────────────┘ ``` **Guidelines:** - One tap target (entire widget) - Essential info only - Clear visual hierarchy - No buttons or complex interactions ### Medium Widget Design ``` ┌─────────────────────────────────────────┐ │ [Icon] │ │ Title ┌─────────────┐ │ │ Subtitle │ Action │ │ │ └─────────────┘ │ │ Additional context │ └─────────────────────────────────────────┘ ``` **Guidelines:** - Can have multiple tap targets - Show 2-4 pieces of information - Actions should be quick (open to specific view) ### Large Widget Design ``` ┌─────────────────────────────────────────┐ │ Header Edit │ ├─────────────────────────────────────────┤ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Item 1 │ │ Item 2 │ │ Item 3 │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Item 4 │ │ Item 5 │ │ Item 6 │ │ │ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────┘ ``` **Guidelines:** - Multiple tap targets allowed - Show a collection or dashboard - Include clear visual grouping - Optional: Edit configuration ### Lock Screen Widget Design Lock Screen widgets have limited space and no color. **Circular:** ``` ┌─────┐ │ 73° │ Temperature │ ☀️ │ Weather icon └─────┘ ``` **Rectangular:** ``` ┌─────────────────────┐ │ Next Event │ │ Team Meeting @ 2pm │ └─────────────────────┘ ``` **Best practices:** - Design for small size - Use SF Symbols (render well) - Test in Light and Dark modes - Consider StandBy mode (larger display) --- ## Widget Configuration ### User-Configurable Widgets Allow users to customize what the widget shows: ```swift struct ConfigurationIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Configuration" @Parameter(title: "City") var city: City? @Parameter(title: "Units") var units: TemperatureUnit } ``` **Configuration UI:** - Keep options simple (few parameters) - Provide sensible defaults - Preview changes before confirming ### Widget Families Support multiple sizes: ```swift struct MyWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in MyWidgetView(entry: entry) } .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) } } ``` --- ## App Clips ### What App Clips Are App Clips are lightweight versions of your app (<10MB) for quick, focused tasks without full installation. **Invocation points:** - NFC tags - QR codes - App Clip codes - Safari Smart App Banner - Maps - Messages ### App Clip Design Principles **1. Focus on one task** - Rent a bike - Order food - Pay for parking **2. Minimize required information** - Only ask for what's essential - Use Sign in with Apple - Use Apple Pay **3. Fast experience** - User expects to finish in under a minute - No lengthy onboarding - Minimal UI, maximum function **4. Encourage full app download** - Show value of full app - Make download easy (banner) - Don't block functionality to force download ### App Clip UI Guidelines ``` ┌─────────────────────────────────────────┐ │ [Header: What you can do] │ ├─────────────────────────────────────────┤ │ │ │ [Primary action UI] │ │ │ │ ┌─────────────────────────────────┐ │ │ │ [Apple Pay] │ │ │ └─────────────────────────────────┘ │ │ │ ├─────────────────────────────────────────┤ │ Get the full app for more features │ │ ┌─────────────────────────────────┐ │ │ │ Download App │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────┘ ``` ### App Clip Code Design App Clip Codes are scannable codes that launch App Clips: ``` ┌─────────────────┐ ╱ ╲ │ [App Clip Code] │ │ Circular pattern │ │ with NFC chip │ ╲ ╱ └─────────────────┘ Scan or tap to rent a scooter ``` **Placement guidelines:** - Clear call to action below code - Explain what will happen - Accessible height (3.5-5 feet) - Well-lit, clean surface --- ## Share Extensions ### Share Extension Design ``` ┌─────────────────────────────────────────┐ │ Post to [App Name] ✕ │ ├─────────────────────────────────────────┤ │ ┌─────────────────────────────────┐ │ │ │ [Preview of content] │ │ │ └─────────────────────────────────┘ │ │ │ │ Add a comment... │ │ │ │ ┌─────────────────────────────────┐ │ │ │ Share │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────┘ ``` **Guidelines:** - Show preview of shared content - Minimal configuration options - Quick completion (< 10 seconds ideal) - Clear success/error feedback --- ## Action Extensions ### Action Extension Design Action extensions process content in place: ``` ┌─────────────────────────────────────────┐ │ Markup Done │ ├─────────────────────────────────────────┤ │ │ │ [Modified content preview] │ │ │ ├─────────────────────────────────────────┤ │ [Tools for modification] │ └─────────────────────────────────────────┘ ``` **Guidelines:** - Focus on specific task - Return modified content to host app - Match system UI conventions - Support undo/cancel --- ## Live Activities ### What Live Activities Are Real-time updates on Lock Screen and Dynamic Island for ongoing events: - Sports scores - Delivery tracking - Timers - Ride sharing ### Live Activity Design **Lock Screen (expanded):** ``` ┌─────────────────────────────────────────┐ │ [Leading] [Center] [Trailing] │ │ Team A vs Team B │ │ 24 Q3 21 │ └─────────────────────────────────────────┘ ``` **Dynamic Island (compact):** ``` ┌──────────────────────────────────────┐ │ 🏀 24 - 21 Q3 │ └──────────────────────────────────────┘ ``` **Dynamic Island (expanded):** ``` ┌────────────────────────────────────────┐ │ Lakers vs Celtics │ │ 24 21 │ │ ──────────────────────────────────── │ │ Q3 • 4:32 remaining │ └────────────────────────────────────────┘ ``` ### Live Activity Guidelines **Do:** - Update only when meaningful changes occur - Design for all Dynamic Island states - Provide clear end states - Respect 8-hour maximum duration **Don't:** - Update every second (unless timer) - Show static content - Use for notifications - Require interaction to see status --- ## Widget Development Tips ### Timeline Updates ```swift func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { let entries = [ SimpleEntry(date: Date(), data: currentData), SimpleEntry(date: Date().addingTimeInterval(60*15), data: futureData) ] let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } ``` **Update policies:** - `.atEnd` - Update when all entries displayed - `.after(date)` - Update at specific time - `.never` - Only update on user action ### Deep Links Widgets should link to specific content: ```swift Link(destination: URL(string: "myapp://item/\(item.id)")!) { ItemView(item: item) } ``` ### Placeholder Design Show meaningful placeholder while loading: ```swift struct PlaceholderView: View { var body: some View { VStack { RoundedRectangle(cornerRadius: 8) .fill(Color.gray.opacity(0.3)) RoundedRectangle(cornerRadius: 4) .fill(Color.gray.opacity(0.2)) } } } ```
-
-
SKILL.md 19.9 KB
--- name: ios-hig-design description: 'Design native iOS interfaces following Apple Human Interface Guidelines. Use when the user mentions "iPhone app", "iPad layout", "SwiftUI", "UIKit", "Dynamic Island", "safe areas", "HIG compliance", "SF Symbols", "haptic feedback", "iOS accessibility", "make my app feel native", or "follow Apple design guidelines". Also trigger when building tab bars, navigation stacks, sheets, or modals for iOS, implementing dark mode, or adapting layouts across screen sizes. Covers navigation patterns, accessibility, SF Symbols, and platform conventions. For general UI polish, see refactoring-ui. For affordance design, see design-everyday-things.' license: MIT metadata: author: wondelai version: "1.5.1" --- # iOS Human Interface Guidelines Design Skill Framework for designing native iOS interfaces that feel intuitive, consistent, and aligned with Apple's design philosophy. Based on Apple's Human Interface Guidelines, the definitive resource for apps that integrate seamlessly with iPhone, iPad, and the Apple ecosystem. ## Core Principle Apple's iOS design philosophy rests on three pillars: **clarity** (every element legible and purposeful), **deference** (the interface never overshadows the content it presents), and **depth** (layering, transitions, and realistic motion convey hierarchy and spatial relationships). **The foundation:** The best iOS apps internalize this philosophy rather than following HIG rules mechanically. Native components, system conventions, and platform consistency aren't constraints---they're the reason iOS users trust and enjoy apps that feel like they belong. ## Scoring **Goal: 10/10.** Score 1 point per satisfied row of the Quick Diagnostic (6 rows), plus up to 4 points for native idiom: +1 semantic colors/text styles throughout (no hardcoded values), +1 system controls over custom reimplementations, +1 standard gestures and meaningful haptics, +1 SF Symbols and correct app-icon shape. Bands: **9-10** = native, accessible, adapts to Dark Mode and Dynamic Type, zero foreign patterns; **5-6** = works but leaks Android idioms or hardcodes color/size; **<=3** = fails safe areas, touch targets, or VoiceOver. Always state the score and the specific improvements needed to reach 10/10. ## iOS Design Framework ### 1. Layout & Safe Areas **Core concept:** iOS devices have specific screen dimensions, safe area insets, and hardware intrusions (notch, Dynamic Island, home indicator) that every layout must respect. **Key insights:** - Design for the smallest screen first (375pt width, iPhone SE) - Safe areas protect content from the notch, Dynamic Island, and home indicator---never place interactive elements under them - Standard content margins: 16-20pt from screen edges; spacing increments: 8 / 16 / 24pt - Minimum touch target and list row height: 44pt **Product applications:** | Context | Layout Pattern | Example | |---------|---------------|---------| | **Status bar** | 20pt classic, 44-54pt on Dynamic Island devices | Time, signal, battery area | | **Navigation bar** | 44pt standard row + ~52pt large title (~96pt total) | Back button, title, actions | | **Content area** | Flexible, scrollable, respects safe area | Main app content | | **Tab bar** | 49pt height, translucent with blur | 2-5 primary destinations | | **Home indicator** | 34pt inset at bottom | System gesture area | **Copy patterns:** - Use `VStack { }`, which respects safe areas by default - Use `.ignoresSafeArea()` only for backgrounds and decoration, never interactive content - Test on multiple sizes, including iPhone SE and Pro Max See [references/navigation.md](references/navigation.md) when laying out chrome---exact nav bar and tab bar dimensions, large-title behavior, and split-view rules. ### 2. Typography & Dynamic Type **Core concept:** iOS uses the San Francisco (SF Pro) typeface with semantic text styles that automatically scale for accessibility via Dynamic Type. Semantic styles give consistent platform hierarchy; Dynamic Type lets users read at their preferred size without breaking layouts. **Key insights:** - Large Title: 34pt Bold; Title: 17pt Medium; Body: 17pt Regular; Caption: 12-13pt; secondary text: 15pt at 60% opacity - Minimum text size 11pt (captions/secondary only) - Line height at least 1.3x font size; optimal line length 35-50 characters on mobile - Always left-aligned, non-justified text **Product applications:** | Context | Typography Pattern | Example | |---------|-------------------|---------| | **Screen titles** | `.largeTitle` or `.title` style | Large title collapses on scroll | | **Body content** | `.body` style, 17pt | List items, descriptions | | **Secondary info** | `.subheadline` or `.footnote` | Timestamps, metadata | | **Tab labels** | 10pt SF text | Tab bar item labels | | **Buttons** | `.body` weight semibold | Primary action text | **Copy patterns:** - Use `.font(.title)`, `.font(.body)`, `.font(.caption)` instead of hardcoded sizes; `@ScaledMetric` for custom spacing that scales - Prefer weight and color variation over extreme size differences for hierarchy - Test all layouts at the largest Dynamic Type size See [references/typography.md](references/typography.md) when matching a design to exact specs---per-style hex values and the Dark Mode text-color mapping. ### 3. Color & Dark Mode **Core concept:** iOS provides semantic system colors that automatically adapt between light and dark appearances while preserving contrast and hierarchy. **Key insights:** - Use `Color(.label)`, `Color(.secondaryLabel)`, `Color(.systemBackground)` instead of hardcoded colors - `Color(.systemBlue)` is the default tint; `.systemRed` for destructive actions; `.systemGreen` for success - Dark Mode inverts text colors and shifts backgrounds darker while keeping relative hierarchy; accent colors need lower brightness and higher saturation to pop - Maintain 4.5:1 contrast in both modes; preview both during development **Product applications:** | Context | Color Pattern | Example | |---------|--------------|---------| | **Primary text** | `Color(.label)` | Adapts white/black per mode | | **Secondary text** | `Color(.secondaryLabel)` | 60% opacity in both modes | | **Backgrounds** | `Color(.systemBackground)` / `.secondarySystemBackground` | Layered depth | | **Destructive actions** | `Color(.systemRed)` | Delete buttons, warnings | | **Interactive tint** | App accent color or `.systemBlue` | Links, toggle states | **Copy patterns:** - Use `.preferredColorScheme(.light)` and `.dark` in previews to test both modes side by side - Define custom colors in the Asset Catalog with light/dark variants, not in code - Never assume a background is white or black; test with Increase Contrast enabled See [references/colors-depth.md](references/colors-depth.md) when checking contrast---the full WCAG ratio table (normal text, large text, UI components) and the tertiary/grouped-background tokens. ### 4. Navigation Patterns **Core concept:** iOS uses a layered navigation model: tab bars for primary destinations, navigation stacks for hierarchical drilling, and modals for focused tasks. Users rely on these patterns to know where they are and how to get back; reinventing them makes the app feel foreign. **Key insights:** - Tab bar: 2-5 primary destinations, always visible, remembers state per tab - Navigation bar: back button (top-left), title (center or large), actions (top-right); large title collapses on scroll - Modals for focused tasks; dismiss via swipe-down or explicit close button - Never use hamburger menus---iOS users expect tab bars - Search bar can sit below the nav bar, hidden until pulled down **Product applications:** | Context | Navigation Pattern | Example | |---------|-------------------|---------| | **App structure** | Tab bar with 3-5 tabs | Home, Search, Profile | | **Content hierarchy** | Push navigation (drill-down) | List > Detail > Edit | | **Focused tasks** | Modal presentation | Compose, settings, filters | | **Search** | Pull-down search bar | Spotlight-style search | | **Split view** | iPad sidebar + detail | Mail, Notes on iPad | **Copy patterns:** - Back button text should be the previous screen's title, not "Back" - Tab labels are single words ("Home", "Search"); modal titles describe the task ("New Message", "Edit Profile") - Use `NavigationStack` (not deprecated `NavigationView`) in SwiftUI ### 5. Controls & Inputs **Core concept:** iOS provides a rich library of native controls (buttons, lists, toggles, pickers, menus, text fields) that users already understand and expect. **Why it works:** Native controls ship with built-in accessibility, haptics, and learned interaction patterns; custom controls create friction and miss edge cases Apple already solved. **Key insights:** - Page-level actions go in the nav bar (top) or action bar (bottom) - Primary buttons are filled with the theme color; secondary are outlined or text-only - Destructive actions use red and require confirmation when irreversible - Lists (table views) are the fundamental iOS content pattern - Match keyboard type to input (`.emailAddress`, `.phonePad`, `.URL`); use `.textContentType` for autofill **Product applications:** | Context | Control Pattern | Example | |---------|----------------|---------| | **Forms** | Native text fields with proper keyboard types | Email field with @ keyboard | | **Settings** | Grouped list with toggles, disclosure | iOS Settings style | | **Selection** | Picker, segmented control, or action sheet | Date picker, sort options | | **Destructive actions** | Red button + confirmation alert | "Delete Account" flow | | **Context actions** | Long press menu or swipe actions | Edit, share, delete on row | **Copy patterns:** - Pair `.keyboardType(.emailAddress)` with `.textContentType(.emailAddress)` - Prefer system confirmations: `.alert()` or `.confirmationDialog()`; use `.swipeActions` on list rows - Place primary action buttons at the bottom of the screen within thumb reach **Ethical boundary:** Never disguise ads as native controls or make destructive actions easy to trigger accidentally. See [references/components.md](references/components.md) when building a specific control---button styles, list/section variants, picker vs segmented-control choice, and confirmation-dialog wiring. See [references/keyboard-input.md](references/keyboard-input.md) when building forms---keyboard-type table, input accessory views, and hardware-keyboard shortcuts. ### 6. Accessibility **Core concept:** iOS has world-class accessibility features (VoiceOver, Dynamic Type, Switch Control, Voice Control), and every app must support them as a first-class concern. App Store review can reject apps that are unusable with assistive technologies. **Key insights:** - Every interactive element needs an `.accessibilityLabel`; use `.accessibilityValue` for state and `.accessibilityHint` for effect - Group related elements with `.accessibilityElement(children: .combine)` - Support Dynamic Type at all sizes; test at the largest setting - Honor the 44 x 44pt touch target (section 1) and 4.5:1 contrast minimum (section 3) as accessibility requirements, not just visual defaults - Never convey meaning through color alone **Product applications:** | Context | Accessibility Pattern | Example | |---------|----------------------|---------| | **Icons** | `.accessibilityLabel("Favorite")` | Heart icon with label | | **Sliders** | `.accessibilityValue("\(Int(volume * 100))%")` | Volume control | | **Buttons** | `.accessibilityHint("Shares this item")` | Share button | | **Groups** | `.accessibilityElement(children: .combine)` | Avatar + name row | | **Images** | Decorative: `.accessibilityHidden(true)` | Background patterns | **Copy patterns:** - Write labels as nouns ("Favorite", "Settings"); write hints as actions ("Shares this item with others") - Test the complete app flow using only VoiceOver - Use Xcode's Accessibility Inspector to audit contrast and labels See [references/accessibility.md](references/accessibility.md) before sign-off---the full VoiceOver-implementation patterns and a pre-ship accessibility checklist to run the app against. ### 7. Icons & Images **Core concept:** iOS uses SF Symbols as the standard icon system and requires app icons in specific sizes with the signature superellipse ("squircle") mask applied automatically. SF Symbols align optically with San Francisco text and scale with Dynamic Type, so they stay aligned and crisp at every weight and size. **Key insights:** - Use SF Symbols (`Image(systemName:)`) for all standard icons---they scale with text - App icons: export 1024x1024px square; iOS applies the squircle mask (corner radius = side x 0.222 with 61% smoothing) - iOS 18+ supports light, dark, and tinted icon variants - Avoid text in app icons; keep designs simple with recognizable silhouettes **Product applications:** | Context | Icon Pattern | Example | |---------|-------------|---------| | **Tab bar** | SF Symbols, filled variant for selected | `house.fill`, `magnifyingglass` | | **Navigation bar** | SF Symbols at regular weight | `gear`, `plus`, `ellipsis` | | **List accessories** | SF Symbols, secondary color | `chevron.right`, `checkmark` | | **App icon** | 1024px square, simple bold design | Single recognizable glyph | **Copy patterns:** - Use `Image(systemName: "heart.fill")`; apply `.symbolRenderingMode(.hierarchical)` for multi-color depth - Size symbols relative to text with `.imageScale(.large)` or `.font()` - Browse symbols in the free SF Symbols app from Apple **Ethical boundary:** Never use icons that suggest functionality that doesn't exist or contradict iOS conventions (trash = delete, not archive). See [references/app-icons.md](references/app-icons.md) when exporting the app icon---per-context size table, exact squircle math, and the iOS 18 light/dark/tinted variant requirements. ### 8. Gestures & Haptics **Core concept:** iOS defines standard gestures (swipe back, pull to refresh, long press for context menu) and haptic feedback patterns that must be respected and never overridden. Gestures are muscle memory---repurposing swipe-back or pull-to-refresh disorients users; haptics give invisible confirmation that an action registered. **Key insights:** - Never override: swipe-right-from-edge (back), swipe-down on modal (dismiss), pull-down on list (refresh) - Swipe-left on rows reveals actions; long press shows context menus; pinch zooms images and maps - Three haptic types: impact (physical actions), notification (outcomes), selection (UI changes) - Haptics should be subtle and meaningful---never constant or annoying **Product applications:** | Context | Gesture/Haptic Pattern | Example | |---------|----------------------|---------| | **Navigation** | Swipe right from left edge | System back gesture | | **Modals** | Swipe down to dismiss | Sheet dismissal | | **Lists** | Pull to refresh, swipe for actions | Refresh content, delete row | | **Confirmation** | `.success` haptic on completion | Payment confirmed | | **Selection** | Selection haptic on toggle/pick | Picker wheel scroll | **Copy patterns:** - `UIImpactFeedbackGenerator(style: .medium)` for physical interactions; `UISelectionFeedbackGenerator()` for UI state changes - `UINotificationFeedbackGenerator()` with `.success`, `.warning`, `.error` for outcomes - Call `.prepare()` before triggering haptics to minimize latency See [references/gestures.md](references/gestures.md) when wiring gestures or animation---the full reserved-gesture table, haptic-generator recipes, and standard animation timing/curves. ## Common Mistakes | Mistake | Why It Fails | Fix | |---------|-------------|-----| | **Overriding standard gestures** | Breaks muscle memory for swipe-back, pull-refresh | Use system gestures as intended; custom gestures only for supplementary actions | | **Touch targets under 44pt** | Mis-taps, frustration, accessibility failures | Make all interactive elements at least 44 x 44pt | | **Ignoring safe areas** | Content hidden behind notch, Dynamic Island, home indicator | Respect safe area insets; `.ignoresSafeArea()` only for backgrounds | | **Using Android patterns on iOS** | Hamburger menus, top tabs, FABs feel foreign | Use tab bars, bottom sheets, native iOS components | | **Skipping Dark Mode** | Broken layouts, unreadable text for Dark Mode users | Use semantic colors; test both appearances | | **Hardcoding font sizes** | Breaks Dynamic Type, excludes low-vision users | Use semantic text styles (`.title`, `.body`, `.caption`) throughout | | **Low contrast text** | Fails WCAG AA; unreadable in sunlight | Maintain 4.5:1 minimum; test with Increase Contrast | | **Not testing on real devices** | Simulator misses performance, haptics, safe area edge cases | Test on physical devices at smallest and largest sizes | ## Quick Diagnostic Audit any iOS interface design: | Question | If No | Action | |----------|-------|--------| | Does the layout respect safe areas on all device sizes? | Content hidden behind hardware | Audit on iPhone SE and Pro Max; fix insets | | Are all touch targets at least 44 x 44pt? | Mis-taps and accessibility failures | Increase tap areas; `.frame(minWidth: 44, minHeight: 44)` | | Does the app work fully in Dark Mode? | Broken/unreadable UI for Dark Mode users | Replace hardcoded colors with semantic system colors | | Does text scale properly with Dynamic Type? | Excludes low-vision users | Use semantic text styles; test at largest setting | | Can a VoiceOver user complete every task? | App inaccessible to blind users | Add labels, values, hints to all interactive elements | | Are navigation patterns native iOS? | App feels foreign | Replace hamburger menus with tab bars; standard push/modal navigation | ## Beyond Core UI The eight framework sections above each link their deep-dive reference inline at the point of need. Three further references cover system surfaces that sit outside the on-screen UI: - See [references/privacy-permissions.md](references/privacy-permissions.md) when the app requests camera, location, contacts, or any protected resource---request timing, pre-permission priming screens, usage-string wording, and the denied-permission recovery path. - See [references/widgets-extensions.md](references/widgets-extensions.md) when building a Home Screen widget, App Clip, Live Activity, or share/action extension---supported sizes and per-surface design constraints. - See [references/system-integration.md](references/system-integration.md) when wiring the app into the OS---Siri/Shortcuts intents, Handoff, drag-and-drop, universal links, and Spotlight indexing. ## Further Reading For the complete guidelines, platform-specific guidance, and latest updates: - [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) --- the definitive reference for all Apple platforms - [SF Symbols](https://developer.apple.com/sf-symbols/) --- Apple's icon system, 5,000+ configurable symbols - [Apple Design Resources](https://developer.apple.com/design/resources/) --- official Figma/Sketch templates and UI kits - [WWDC Design Sessions](https://developer.apple.com/videos/design/) --- videos on design principles and new features - *"Designed by Apple in California"* --- photo book of Apple's design process (out of print; Apple no longer sells it) - [*"The Design of Everyday Things"*](https://www.amazon.com/Design-Everyday-Things-Revised-Expanded/dp/0465050654?tag=wondelai00-20) by Don Norman --- the human-centered design text that influenced Apple - [*"Universal Principles of Design"*](https://www.amazon.com/Universal-Principles-Design-Revised-Updated/dp/1592535879?tag=wondelai00-20) by William Lidwell, Kritina Holden, and Jill Butler --- 125 principles applicable to iOS ## About the Author The **Apple Human Interface Guidelines** are written and maintained by Apple's Human Interface Design team, one of the most influential design organizations in technology. First published in 1984 alongside the original Macintosh, the HIG established principles---direct manipulation, consistency, user control---that defined graphical interface design and have evolved through iPhone, iPad, Apple Watch, and Vision Pro. It remains freely available at developer.apple.com as the essential reference for Apple platforms.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.