ios-design-guidelines

安装量: 451
排名: #2259

安装

npx skills add https://github.com/ehmo/platform-design-skills --skill ios-design-guidelines

iOS Design Guidelines for iPhone Comprehensive rules derived from Apple's Human Interface Guidelines. Apply these when building, reviewing, or refactoring any iPhone app interface. 1. Layout & Safe Areas Impact: CRITICAL Rule 1.1: Minimum 44pt Touch Targets All interactive elements must have a minimum tap target of 44x44 points. This includes buttons, links, toggles, and custom controls. Correct: Button ( "Save" ) { save ( ) } . frame ( minWidth : 44 , minHeight : 44 ) Incorrect: // 20pt icon with no padding — too small to tap reliably Button ( action : save ) { Image ( systemName : "checkmark" ) . font ( . system ( size : 20 ) ) } // Missing .frame(minWidth: 44, minHeight: 44) Rule 1.2: Respect Safe Areas Never place interactive or essential content under the status bar, Dynamic Island, or home indicator. Use SwiftUI's automatic safe area handling or UIKit's safeAreaLayoutGuide . Correct: struct ContentView : View { var body : some View { VStack { Text ( "Content" ) } // SwiftUI respects safe areas by default } } Incorrect: struct ContentView : View { var body : some View { VStack { Text ( "Content" ) } . ignoresSafeArea ( ) // Content will be clipped under notch/Dynamic Island } } Use .ignoresSafeArea() only for background fills, images, or decorative elements — never for text or interactive controls. Rule 1.3: Primary Actions in the Thumb Zone Place primary actions at the bottom of the screen where the user's thumb naturally rests. Secondary actions and navigation belong at the top. Correct: VStack { ScrollView { / content / } Button ( "Continue" ) { next ( ) } . buttonStyle ( . borderedProminent ) . padding ( ) } Incorrect: VStack { Button ( "Continue" ) { next ( ) } // Top of screen — hard to reach one-handed . buttonStyle ( . borderedProminent ) . padding ( ) ScrollView { / content / } } Rule 1.4: Support All iPhone Screen Sizes Design for iPhone SE (375pt wide) through iPhone Pro Max (430pt wide). Use flexible layouts, avoid hardcoded widths. Correct: HStack ( spacing : 12 ) { ForEach ( items ) { item in CardView ( item : item ) . frame ( maxWidth : . infinity ) // Adapts to screen width } } Incorrect: HStack ( spacing : 12 ) { ForEach ( items ) { item in CardView ( item : item ) . frame ( width : 180 ) // Breaks on SE, wastes space on Pro Max } } Rule 1.5: 8pt Grid Alignment Align spacing, padding, and element sizes to multiples of 8 points (8, 16, 24, 32, 40, 48). Use 4pt for fine adjustments. Rule 1.6: Landscape Support Support landscape orientation unless the app is task-specific (e.g., camera). Use ViewThatFits or GeometryReader for adaptive layouts. 2. Navigation Impact: CRITICAL Rule 2.1: Tab Bar for Top-Level Sections Use a tab bar at the bottom of the screen for 3 to 5 top-level sections. Each tab should represent a distinct category of content or functionality. Correct: TabView { HomeView ( ) . tabItem { Label ( "Home" , systemImage : "house" ) } SearchView ( ) . tabItem { Label ( "Search" , systemImage : "magnifyingglass" ) } ProfileView ( ) . tabItem { Label ( "Profile" , systemImage : "person" ) } } Incorrect: // Hamburger menu hidden behind three lines — discoverability is near zero NavigationView { Button ( action : { showMenu . toggle ( ) } ) { Image ( systemName : "line.horizontal.3" ) } } Rule 2.2: Never Use Hamburger Menus Hamburger (drawer) menus hide navigation, reduce discoverability, and violate iOS conventions. Use a tab bar instead. If you have more than 5 sections, consolidate or use a "More" tab. Rule 2.3: Large Titles in Primary Views Use .navigationBarTitleDisplayMode(.large) for top-level views. Titles transition to inline ( .inline ) when the user scrolls. Correct: NavigationStack { List ( items ) { item in ItemRow ( item : item ) } . navigationTitle ( "Messages" ) . navigationBarTitleDisplayMode ( . large ) } Rule 2.4: Never Override Back Swipe The swipe-from-left-edge gesture for back navigation is a system-level expectation. Never attach custom gesture recognizers that interfere with it. Incorrect: . gesture ( DragGesture ( ) . onChanged { / custom drawer / } // Conflicts with system back swipe ) Rule 2.5: Use NavigationStack for Hierarchical Content Use NavigationStack (not the deprecated NavigationView ) for drill-down content. Use NavigationPath for programmatic navigation. Correct: NavigationStack ( path : $path ) { List ( items ) { item in NavigationLink ( value : item ) { ItemRow ( item : item ) } } . navigationDestination ( for : Item . self ) { item in ItemDetail ( item : item ) } } Rule 2.6: Preserve State Across Navigation When users navigate back and then forward, or switch tabs, restore the previous scroll position and input state. Use @SceneStorage or @State to persist view state. 3. Typography & Dynamic Type Impact: HIGH Rule 3.1: Use Built-in Text Styles Always use semantic text styles rather than hardcoded sizes. These scale automatically with Dynamic Type. Correct: VStack ( alignment : . leading , spacing : 4 ) { Text ( "Section Title" ) . font ( . headline ) Text ( "Body content that explains the section." ) . font ( . body ) Text ( "Last updated 2 hours ago" ) . font ( . caption ) . foregroundStyle ( . secondary ) } Incorrect: VStack ( alignment : . leading , spacing : 4 ) { Text ( "Section Title" ) . font ( . system ( size : 17 , weight : . semibold ) ) // Won't scale with Dynamic Type Text ( "Body content" ) . font ( . system ( size : 15 ) ) // Won't scale with Dynamic Type } Rule 3.2: Support Dynamic Type Including Accessibility Sizes Dynamic Type can scale text up to approximately 200% at the largest accessibility sizes. Layouts must reflow — never truncate or clip essential text. Correct: HStack { Image ( systemName : "star" ) Text ( "Favorites" ) . font ( . body ) } // At accessibility sizes, consider using ViewThatFits or // AnyLayout to switch from HStack to VStack Use @Environment(.dynamicTypeSize) to detect size category and adapt layouts: @Environment ( \ . dynamicTypeSize ) var dynamicTypeSize var body : some View { if dynamicTypeSize . isAccessibilitySize { VStack { content } } else { HStack { content } } } Rule 3.3: Custom Fonts Must Use UIFontMetrics If you use a custom typeface, scale it with UIFontMetrics so it responds to Dynamic Type. Correct: extension Font { static func scaledCustom ( size : CGFloat , relativeTo textStyle : Font . TextStyle ) -> Font { . custom ( "CustomFont-Regular" , size : size , relativeTo : textStyle ) } } // Usage Text ( "Hello" ) . font ( . scaledCustom ( size : 17 , relativeTo : . body ) ) Rule 3.4: SF Pro as System Font Use the system font (SF Pro) unless brand requirements dictate otherwise. SF Pro is optimized for legibility on Apple displays. Rule 3.5: Minimum 11pt Text Never display text smaller than 11pt. Prefer 17pt for body text. Use the caption2 style (11pt) as the absolute minimum. Rule 3.6: Hierarchy Through Weight and Size Establish visual hierarchy through font weight and size. Do not rely solely on color to differentiate text levels. 4. Color & Dark Mode Impact: HIGH Rule 4.1: Use Semantic System Colors Use system-provided semantic colors that automatically adapt to light and dark modes. Correct: Text ( "Primary text" ) . foregroundStyle ( . primary ) // Adapts to light/dark Text ( "Secondary info" ) . foregroundStyle ( . secondary ) VStack { } . background ( Color ( . systemBackground ) ) // White in light, black in dark Incorrect: Text ( "Primary text" ) . foregroundColor ( . black ) // Invisible on dark backgrounds VStack { } . background ( . white ) // Blinding in Dark Mode Rule 4.2: Provide Light and Dark Variants for Custom Colors Define custom colors in the asset catalog with both Any Appearance and Dark Appearance variants. // In Assets.xcassets, define "BrandBlue" with: // Any Appearance: #0066CC // Dark Appearance: #4DA3FF Text ( "Brand text" ) . foregroundStyle ( Color ( "BrandBlue" ) ) // Automatically switches Rule 4.3: Never Rely on Color Alone Always pair color with text, icons, or shapes to convey meaning. Approximately 8% of men have some form of color vision deficiency. Correct: HStack { Image ( systemName : "exclamationmark.triangle.fill" ) . foregroundStyle ( . red ) Text ( "Error: Invalid email address" ) . foregroundStyle ( . red ) } Incorrect: // Only color indicates the error — invisible to colorblind users TextField ( "Email" , text : $email ) . border ( isValid ? . green : . red ) Rule 4.4: 4.5:1 Contrast Ratio Minimum All text must meet WCAG AA contrast ratios: 4.5:1 for normal text, 3:1 for large text (18pt+ or 14pt+ bold). Rule 4.5: Support Display P3 Wide Gamut Use Display P3 color space for vibrant, accurate colors on modern iPhones. Define colors in the asset catalog with the Display P3 gamut. Rule 4.6: Background Hierarchy Use the three-level background hierarchy for depth: systemBackground — primary surface secondarySystemBackground — grouped content, cards tertiarySystemBackground — elements within grouped content Rule 4.7: One Accent Color for Interactive Elements Choose a single tint/accent color for all interactive elements (buttons, links, toggles). This creates a consistent, learnable visual language. @main struct MyApp : App { var body : some Scene { WindowGroup { ContentView ( ) . tint ( . indigo ) // All interactive elements use indigo } } } 5. Accessibility Impact: CRITICAL Rule 5.1: VoiceOver Labels on All Interactive Elements Every button, control, and interactive element must have a meaningful accessibility label. Correct: Button ( action : addToCart ) { Image ( systemName : "cart.badge.plus" ) } . accessibilityLabel ( "Add to cart" ) Incorrect: Button ( action : addToCart ) { Image ( systemName : "cart.badge.plus" ) } // VoiceOver reads "cart.badge.plus" — meaningless to users Rule 5.2: Logical VoiceOver Navigation Order Ensure VoiceOver reads elements in a logical order. Use .accessibilitySortPriority() to adjust when the visual layout doesn't match the reading order. VStack { Text ( "Price: $29.99" ) . accessibilitySortPriority ( 1 ) // Read first Text ( "Product Name" ) . accessibilitySortPriority ( 2 ) // Read second } Rule 5.3: Support Bold Text When the user enables Bold Text in Settings, use the .bold dynamic type variants. SwiftUI text styles handle this automatically. Custom text must respond to UIAccessibility.isBoldTextEnabled . Rule 5.4: Support Reduce Motion Disable decorative animations and parallax when Reduce Motion is enabled. Use @Environment(.accessibilityReduceMotion) . Correct: @Environment ( \ . accessibilityReduceMotion ) var reduceMotion var body : some View { CardView ( ) . animation ( reduceMotion ? nil : . spring ( ) , value : isExpanded ) } Rule 5.5: Support Increase Contrast When the user enables Increase Contrast, ensure custom colors have higher-contrast variants. Use @Environment(.colorSchemeContrast) to detect. Rule 5.6: Don't Convey Info Only by Color, Shape, or Position Information must be available through multiple channels. Pair visual indicators with text or accessibility descriptions. Rule 5.7: Alternative Interactions for All Gestures Every custom gesture must have an equivalent tap-based or menu-based alternative for users who cannot perform complex gestures. Rule 5.8: Support Switch Control and Full Keyboard Access Ensure all interactions work with Switch Control (external switches) and Full Keyboard Access (Bluetooth keyboards). Test navigation order and focus behavior. 6. Gestures & Input Impact: HIGH Rule 6.1: Use Standard Gestures Use the standard iOS gesture vocabulary: tap, long press, swipe, pinch, rotate. Users already understand these. Gesture Standard Use Tap Primary action, selection Long press Context menu, preview Swipe horizontal Delete, archive, navigate back Swipe vertical Scroll, dismiss sheet Pinch Zoom in/out Two-finger rotate Rotate content Rule 6.2: Never Override System Gestures These gestures are reserved by the system and must not be intercepted: Swipe from left edge (back navigation) Swipe down from top-left (Notification Center) Swipe down from top-right (Control Center) Swipe up from bottom (home / app switcher) Rule 6.3: Custom Gestures Must Be Discoverable If you add a custom gesture, provide visual hints (e.g., a grabber handle) and ensure the action is also available through a visible button or menu item. Rule 6.4: Support All Input Methods Design for touch first, but also support: Hardware keyboards (iPad keyboard accessories, Bluetooth keyboards) Assistive devices (Switch Control, head tracking) Pointer input (assistive touch) 7. Components Impact: HIGH Rule 7.1: Button Styles Use the built-in button styles appropriately: .borderedProminent — primary call-to-action .bordered — secondary actions .borderless — tertiary or inline actions .destructive role — red tint for delete/remove Correct: VStack ( spacing : 16 ) { Button ( "Purchase" ) { buy ( ) } . buttonStyle ( . borderedProminent ) Button ( "Add to Wishlist" ) { wishlist ( ) } . buttonStyle ( . bordered ) Button ( "Delete" , role : . destructive ) { delete ( ) } } Rule 7.2: Alerts — Critical Info Only Use alerts sparingly for critical information that requires a decision. Prefer 2 buttons; maximum 3. The destructive option should use .destructive role. Correct: . alert ( "Delete Photo?" , isPresented : $showAlert ) { Button ( "Delete" , role : . destructive ) { deletePhoto ( ) } Button ( "Cancel" , role : . cancel ) { } } message : { Text ( "This photo will be permanently removed." ) } Incorrect: // Alert for non-critical info — should be a banner or toast . alert ( "Tip" , isPresented : $showTip ) { Button ( "OK" ) { } } message : { Text ( "Swipe left to delete items." ) } Rule 7.3: Sheets for Scoped Tasks Present sheets for self-contained tasks. Always provide a way to dismiss (close button or swipe down). Use .presentationDetents() for half-height sheets. . sheet ( isPresented : $showCompose ) { NavigationStack { ComposeView ( ) . navigationTitle ( "New Message" ) . toolbar { ToolbarItem ( placement : . cancellationAction ) { Button ( "Cancel" ) { showCompose = false } } ToolbarItem ( placement : . confirmationAction ) { Button ( "Send" ) { send ( ) } } } } . presentationDetents ( [ . medium , . large ] ) } Rule 7.4: Lists — Inset Grouped Default Use the .insetGrouped list style as the default. Support swipe actions for common operations. Minimum row height is 44pt. Correct: List { Section ( "Recent" ) { ForEach ( recentItems ) { item in ItemRow ( item : item ) . swipeActions ( edge : . trailing ) { Button ( role : . destructive ) { delete ( item ) } label : { Label ( "Delete" , systemImage : "trash" ) } Button { archive ( item ) } label : { Label ( "Archive" , systemImage : "archivebox" ) } . tint ( . blue ) } } } } . listStyle ( . insetGrouped ) Rule 7.5: Tab Bar Behavior Use SF Symbols for tab icons — filled variant for the selected tab, outline for unselected Never hide the tab bar when navigating deeper within a tab Badge important counts with .badge() TabView { MessagesView ( ) . tabItem { Label ( "Messages" , systemImage : "message" ) } . badge ( unreadCount ) } Rule 7.6: Search Place search using .searchable() . Provide search suggestions and support recent searches. NavigationStack { List ( filteredItems ) { item in ItemRow ( item : item ) } . searchable ( text : $searchText , prompt : "Search items" ) . searchSuggestions { ForEach ( suggestions ) { suggestion in Text ( suggestion . title ) . searchCompletion ( suggestion . title ) } } } Rule 7.7: Context Menus Use context menus (long press) for secondary actions. Never use a context menu as the only way to access an action. PhotoView ( photo : photo ) . contextMenu { Button { share ( photo ) } label : { Label ( "Share" , systemImage : "square.and.arrow.up" ) } Button { favorite ( photo ) } label : { Label ( "Favorite" , systemImage : "heart" ) } Button ( role : . destructive ) { delete ( photo ) } label : { Label ( "Delete" , systemImage : "trash" ) } } Rule 7.8: Progress Indicators Determinate ( ProgressView(value:total:) ) for operations with known duration Indeterminate ( ProgressView() ) for unknown duration Never block the entire screen with a spinner 8. Patterns Impact: MEDIUM Rule 8.1: Onboarding — Max 3 Pages, Skippable Keep onboarding to 3 or fewer pages. Always provide a skip option. Defer sign-in until the user needs authenticated features. TabView { OnboardingPage ( image : "wand.and.stars" , title : "Smart Suggestions" , subtitle : "Get personalized recommendations based on your preferences." ) OnboardingPage ( image : "bell.badge" , title : "Stay Updated" , subtitle : "Receive notifications for things that matter to you." ) OnboardingPage ( image : "checkmark.shield" , title : "Private & Secure" , subtitle : "Your data stays on your device." ) } . tabViewStyle ( . page ) . overlay ( alignment : . topTrailing ) { Button ( "Skip" ) { completeOnboarding ( ) } . padding ( ) } Rule 8.2: Loading — Skeleton Views, No Blocking Spinners Use skeleton/placeholder views that match the layout of the content being loaded. Never show a full-screen blocking spinner. Correct: if isLoading { ForEach ( 0 ..< 5 ) { _ in SkeletonRow ( ) // Placeholder matching final row layout . redacted ( reason : . placeholder ) } } else { ForEach ( items ) { item in ItemRow ( item : item ) } } Incorrect: if isLoading { ProgressView ( "Loading..." ) // Blocks the entire view } else { List ( items ) { item in ItemRow ( item : item ) } } Rule 8.3: Launch Screen — Match First Screen The launch storyboard must visually match the initial screen of the app. No splash logos, no branding screens. This creates the perception of instant launch. Rule 8.4: Modality — Use Sparingly Present modal views only when the user must complete or abandon a focused task. Always provide a clear dismiss action. Never stack modals on top of modals. Rule 8.5: Notifications — High Value Only Only send notifications for content the user genuinely cares about. Support actionable notifications. Categorize notifications so users can control them granularly. Rule 8.6: Settings Placement Frequent settings: In-app settings screen accessible from a profile or gear icon Privacy/permission settings: Defer to the system Settings app via URL scheme Never duplicate system-level controls in-app Rule 8.7: Feedback — Visual + Haptic Provide immediate feedback for every user action: Visual state change (button highlight, animation) Haptic feedback for significant actions using UIImpactFeedbackGenerator , UINotificationFeedbackGenerator , or UISelectionFeedbackGenerator Button ( "Complete" ) { let generator = UINotificationFeedbackGenerator ( ) generator . notificationOccurred ( . success ) completeTask ( ) } 9. Privacy & Permissions Impact: HIGH Rule 9.1: Request Permissions in Context Request a permission at the moment the user takes an action that needs it — never at app launch. Correct: Button ( "Take Photo" ) { // Request camera permission only when the user taps this button AVCaptureDevice . requestAccess ( for : . video ) { granted in if granted { showCamera = true } } } Incorrect: // In AppDelegate.didFinishLaunching — too early, no context func application ( _ application : UIApplication , didFinishLaunchingWithOptions ... ) { AVCaptureDevice . requestAccess ( for : . video ) { _ in } CLLocationManager ( ) . requestWhenInUseAuthorization ( ) UNUserNotificationCenter . current ( ) . requestAuthorization ( options : [ . alert ] ) { _ , _ in } } Rule 9.2: Explain Before System Prompt Show a custom explanation screen before triggering the system permission dialog. The system dialog only appears once — if the user denies, the app must direct them to Settings. struct LocationExplanation : View { var body : some View { VStack ( spacing : 16 ) { Image ( systemName : "location.fill" ) . font ( . largeTitle ) Text ( "Find Nearby Stores" ) . font ( . headline ) Text ( "We use your location to show stores within walking distance. Your location is never shared or stored." ) . font ( . body ) . multilineTextAlignment ( . center ) Button ( "Enable Location" ) { locationManager . requestWhenInUseAuthorization ( ) } . buttonStyle ( . borderedProminent ) Button ( "Not Now" ) { dismiss ( ) } . foregroundStyle ( . secondary ) } . padding ( ) } } Rule 9.3: Support Sign in with Apple If the app offers any third-party sign-in (Google, Facebook), it must also offer Sign in with Apple. Present it as the first option. Rule 9.4: Don't Require Accounts Unless Necessary Let users explore the app before requiring sign-in. Gate only features that genuinely need authentication (purchases, sync, social features). Rule 9.5: App Tracking Transparency If you track users across apps or websites, display the ATT prompt. Respect denial — do not degrade the experience for users who opt out. Rule 9.6: Location Button for One-Time Access Use LocationButton for actions that need location once without requesting ongoing permission. LocationButton ( . currentLocation ) { fetchNearbyStores ( ) } . labelStyle ( . titleAndIcon ) 10. System Integration Impact: MEDIUM Rule 10.1: Widgets for Glanceable Data Provide widgets using WidgetKit for information users check frequently. Widgets are not interactive (beyond tapping to open the app), so show the most useful snapshot. Rule 10.2: App Shortcuts for Key Actions Define App Shortcuts so users can trigger key actions from Siri, Spotlight, and the Shortcuts app. struct MyAppShortcuts : AppShortcutsProvider { static var appShortcuts : [ AppShortcut ] { AppShortcut ( intent : StartWorkoutIntent ( ) , phrases : [ "Start a workout in ( . applicationName ) " ] , shortTitle : "Start Workout" , systemImageName : "figure.run" ) } } Rule 10.3: Spotlight Indexing Index app content with CSSearchableItem so users can find it from Spotlight search. Rule 10.4: Share Sheet Integration Support the system share sheet for content that users might want to send elsewhere. Implement UIActivityItemSource or use ShareLink in SwiftUI. ShareLink ( item : article . url ) { Label ( "Share" , systemImage : "square.and.arrow.up" ) } Rule 10.5: Live Activities Use Live Activities and the Dynamic Island for real-time, time-bound events (delivery tracking, sports scores, workouts). Rule 10.6: Handle Interruptions Gracefully Save state and pause gracefully when interrupted by: Phone calls Siri invocations Notifications App switcher FaceTime SharePlay Use scenePhase to detect transitions: @Environment ( \ . scenePhase ) var scenePhase . onChange ( of : scenePhase ) { _ , newPhase in switch newPhase { case . active : resumeActivity ( ) case . inactive : pauseActivity ( ) case . background : saveState ( ) @unknown default : break } } Quick Reference Need Component Notes Top-level sections (3-5) TabView with .tabItem Bottom tab bar, SF Symbols Hierarchical drill-down NavigationStack Large title on root, inline on children Self-contained task .sheet Swipe to dismiss, cancel/done buttons Critical decision .alert 2 buttons preferred, max 3 Secondary actions .contextMenu Long press; must also be accessible elsewhere Scrolling content List with .insetGrouped 44pt min row, swipe actions Text input TextField / TextEditor Label above, validation below Selection (few options) Picker Segmented for 2-5, wheel for many Selection (on/off) Toggle Aligned right in a list row Search .searchable Suggestions, recent searches Progress (known) ProgressView(value:total:) Show percentage or time remaining Progress (unknown) ProgressView() Inline, never full-screen blocking One-time location LocationButton No persistent permission needed Sharing content ShareLink System share sheet Haptic feedback UIImpactFeedbackGenerator .light , .medium , .heavy Destructive action Button(role: .destructive) Red tint, confirm via alert Evaluation Checklist Use this checklist to audit an iPhone app for HIG compliance: Layout & Safe Areas All touch targets are at least 44x44pt No content is clipped under status bar, Dynamic Island, or home indicator Primary actions are in the bottom half of the screen (thumb zone) Layout adapts from iPhone SE to Pro Max without breaking Spacing aligns to the 8pt grid Navigation Tab bar is used for 3-5 top-level sections No hamburger/drawer menus Primary views use large titles Swipe-from-left-edge back navigation works throughout State is preserved when switching tabs Typography All text uses built-in text styles or UIFontMetrics -scaled custom fonts Dynamic Type is supported up to accessibility sizes Layouts reflow at large text sizes (no truncation of essential text) Minimum text size is 11pt Color & Dark Mode App uses semantic system colors or provides light/dark asset variants Dark Mode looks intentional (not just inverted) No information conveyed by color alone Text contrast meets 4.5:1 (normal) or 3:1 (large) Single accent color for interactive elements Accessibility VoiceOver reads all screens logically with meaningful labels Bold Text preference is respected Reduce Motion disables decorative animations Increase Contrast variant exists for custom colors All gestures have alternative access paths Components Alerts are used only for critical decisions Sheets have a dismiss path (button and/or swipe) List rows are at least 44pt tall Tab bar is never hidden during navigation Destructive buttons use the .destructive role Privacy Permissions are requested in context, not at launch Custom explanation shown before each system permission dialog Sign in with Apple offered alongside other providers App is usable without an account for basic features ATT prompt is shown if tracking, and denial is respected System Integration Widgets show glanceable, up-to-date information App content is indexed for Spotlight Share Sheet is available for shareable content App handles interruptions (calls, background, Siri) gracefully Anti-Patterns These are common mistakes that violate the iOS Human Interface Guidelines. Never do these: Hamburger menus — Use a tab bar. Hamburger menus hide navigation and reduce feature discoverability by up to 50%. Custom back buttons that break swipe-back — If you replace the back button, ensure the swipe-from-left-edge gesture still works via NavigationStack . Full-screen blocking spinners — Use skeleton views or inline progress indicators. Blocking spinners make the app feel frozen. Splash screens with logos — The launch screen must mirror the first screen of the app. Branding delays feel artificial. Requesting all permissions at launch — Asking for camera, location, notifications, and contacts on first launch guarantees most will be denied. Hardcoded font sizes — Use text styles. Hardcoded sizes ignore Dynamic Type and accessibility preferences, breaking the app for millions of users. Using only color to indicate state — Red/green for valid/invalid excludes colorblind users. Always pair with icons or text. Alerts for non-critical information — Alerts interrupt flow and require dismissal. Use banners, toasts, or inline messages for tips and non-critical information. Hiding the tab bar on push — Tab bars should remain visible throughout navigation within a tab. Hiding them disorients users. Ignoring safe areas — Using .ignoresSafeArea() on content views causes text and buttons to disappear under the notch, Dynamic Island, or home indicator. Non-dismissable modals — Every modal must have a clear dismiss path (close button, cancel, swipe down). Trapping users in a modal is hostile. Custom gestures without alternatives — A three-finger swipe for undo is unusable for many people. Provide a visible button or menu item as well. Tiny touch targets — Buttons and links smaller than 44pt cause mis-taps, especially in lists and toolbars. Stacked modals — Presenting a sheet on top of a sheet on top of a sheet creates navigation confusion. Use navigation within a single modal instead. Dark Mode as an afterthought — Using hardcoded colors means the app is either broken in Dark Mode or light mode. Always use semantic colors.

返回排行榜