ios-design-guidelines
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseiOS Design Guidelines for iPhone
iPhone版iOS设计指南
Comprehensive rules derived from Apple's Human Interface Guidelines. Apply these when building, reviewing, or refactoring any iPhone app interface.
本指南包含源自Apple人机界面指南的全面规则,适用于构建、评审或重构任何iPhone应用界面的场景。
1. Layout & Safe Areas
1. 布局与安全区域
Impact: CRITICAL
影响级别:CRITICAL
Rule 1.1: Minimum 44pt Touch Targets
规则1.1:最小44pt触摸目标
All interactive elements must have a minimum tap target of 44x44 points. This includes buttons, links, toggles, and custom controls.
Correct:
swift
Button("Save") { save() }
.frame(minWidth: 44, minHeight: 44)Incorrect:
swift
// 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)所有交互元素必须至少有44x44点的点击目标,包括按钮、链接、开关和自定义控件。
正确示例:
swift
Button("Save") { save() }
.frame(minWidth: 44, minHeight: 44)错误示例:
swift
// 20pt图标无内边距——点击区域过小,难以可靠触发
Button(action: save) {
Image(systemName: "checkmark")
.font(.system(size: 20))
}
// 缺少.frame(minWidth: 44, minHeight: 44)Rule 1.2: Respect Safe Areas
规则1.2:尊重安全区域
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 .
safeAreaLayoutGuideCorrect:
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Content")
}
// SwiftUI respects safe areas by default
}
}Incorrect:
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Content")
}
.ignoresSafeArea() // Content will be clipped under notch/Dynamic Island
}
}Use only for background fills, images, or decorative elements — never for text or interactive controls.
.ignoresSafeArea()切勿将交互或核心内容放置在状态栏、Dynamic Island或主屏幕指示器下方。使用SwiftUI的自动安全区域处理或UIKit的。
safeAreaLayoutGuide正确示例:
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Content")
}
// SwiftUI默认尊重安全区域
}
}错误示例:
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Content")
}
.ignoresSafeArea() // 内容会被刘海/Dynamic Island遮挡
}
}仅在背景填充、图片或装饰元素上使用——切勿用于文本或交互控件。
.ignoresSafeArea()Rule 1.3: Primary Actions in the Thumb Zone
规则1.3:主操作置于拇指可达区
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:
swift
VStack {
ScrollView { /* content */ }
Button("Continue") { next() }
.buttonStyle(.borderedProminent)
.padding()
}Incorrect:
swift
VStack {
Button("Continue") { next() } // Top of screen — hard to reach one-handed
.buttonStyle(.borderedProminent)
.padding()
ScrollView { /* content */ }
}将主操作放在屏幕底部,即用户拇指自然触及的位置。次要操作和导航控件应置于顶部。
正确示例:
swift
VStack {
ScrollView { /* 内容 */ }
Button("Continue") { next() }
.buttonStyle(.borderedProminent)
.padding()
}错误示例:
swift
VStack {
Button("Continue") { next() } // 屏幕顶部——单手持机时难以触及
.buttonStyle(.borderedProminent)
.padding()
ScrollView { /* 内容 */ }
}Rule 1.4: Support All iPhone Screen Sizes
规则1.4:适配所有iPhone屏幕尺寸
Design for iPhone SE (375pt wide) through iPhone Pro Max (430pt wide). Use flexible layouts, avoid hardcoded widths.
Correct:
swift
HStack(spacing: 12) {
ForEach(items) { item in
CardView(item: item)
.frame(maxWidth: .infinity) // Adapts to screen width
}
}Incorrect:
swift
HStack(spacing: 12) {
ForEach(items) { item in
CardView(item: item)
.frame(width: 180) // Breaks on SE, wastes space on Pro Max
}
}设计需适配从iPhone SE(375pt宽)到iPhone Pro Max(430pt宽)的所有机型。使用弹性布局,避免硬编码宽度。
正确示例:
swift
HStack(spacing: 12) {
ForEach(items) { item in
CardView(item: item)
.frame(maxWidth: .infinity) // 适配屏幕宽度
}
}错误示例:
swift
HStack(spacing: 12) {
ForEach(items) { item in
CardView(item: item)
.frame(width: 180) // 在SE机型上会断裂,在Pro Max机型上浪费空间
}
}Rule 1.5: 8pt Grid Alignment
规则1.5:8pt网格对齐
Align spacing, padding, and element sizes to multiples of 8 points (8, 16, 24, 32, 40, 48). Use 4pt for fine adjustments.
间距、内边距和元素尺寸需对齐到8点的倍数(8、16、24、32、40、48)。精细调整可使用4pt。
Rule 1.6: Landscape Support
规则1.6:支持横屏模式
Support landscape orientation unless the app is task-specific (e.g., camera). Use or for adaptive layouts.
ViewThatFitsGeometryReader除非应用为特定任务设计(如相机应用),否则需支持横屏模式。使用或实现自适应布局。
ViewThatFitsGeometryReader2. Navigation
2. 导航
Impact: CRITICAL
影响级别:CRITICAL
Rule 2.1: Tab Bar for Top-Level Sections
规则2.1:标签栏用于顶级分区
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:
swift
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
SearchView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person")
}
}Incorrect:
swift
// Hamburger menu hidden behind three lines — discoverability is near zero
NavigationView {
Button(action: { showMenu.toggle() }) {
Image(systemName: "line.horizontal.3")
}
}在屏幕底部使用标签栏管理3至5个顶级分区,每个标签应代表一个独立的内容或功能类别。
正确示例:
swift
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
SearchView()
.tabItem {
Label("Search", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person")
}
}错误示例:
swift
// 汉堡菜单隐藏在三条横线后——可发现性几乎为零
NavigationView {
Button(action: { showMenu.toggle() }) {
Image(systemName: "line.horizontal.3")
}
}Rule 2.2: Never Use Hamburger Menus
规则2.2:切勿使用汉堡菜单
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.
汉堡(抽屉式)菜单会隐藏导航,降低可发现性,违反iOS惯例。请使用标签栏替代。若分区超过5个,可合并或使用“更多”标签。
Rule 2.3: Large Titles in Primary Views
规则2.3:主视图使用大标题
Use for top-level views. Titles transition to inline () when the user scrolls.
.navigationBarTitleDisplayMode(.large).inlineCorrect:
swift
NavigationStack {
List(items) { item in
ItemRow(item: item)
}
.navigationTitle("Messages")
.navigationBarTitleDisplayMode(.large)
}在顶级视图中使用,用户滚动时标题会切换为行内样式()。
.navigationBarTitleDisplayMode(.large).inline正确示例:
swift
NavigationStack {
List(items) { item in
ItemRow(item: item)
}
.navigationTitle("Messages")
.navigationBarTitleDisplayMode(.large)
}Rule 2.4: Never Override Back Swipe
规则2.4:切勿覆盖返回滑动手势
The swipe-from-left-edge gesture for back navigation is a system-level expectation. Never attach custom gesture recognizers that interfere with it.
Incorrect:
swift
.gesture(
DragGesture()
.onChanged { /* custom drawer */ } // Conflicts with system back swipe
)从左边缘滑动返回是系统级的交互预期,切勿添加会干扰该手势的自定义手势识别器。
错误示例:
swift
.gesture(
DragGesture()
.onChanged { /* 自定义抽屉 */ } // 与系统返回滑动手势冲突
)Rule 2.5: Use NavigationStack for Hierarchical Content
规则2.5:使用NavigationStack处理层级内容
Use (not the deprecated ) for drill-down content. Use for programmatic navigation.
NavigationStackNavigationViewNavigationPathCorrect:
swift
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
ItemRow(item: item)
}
}
.navigationDestination(for: Item.self) { item in
ItemDetail(item: item)
}
}使用(而非已弃用的)处理钻取式内容,使用实现程序化导航。
NavigationStackNavigationViewNavigationPath正确示例:
swift
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
规则2.6:跨导航保留状态
When users navigate back and then forward, or switch tabs, restore the previous scroll position and input state. Use or to persist view state.
@SceneStorage@State当用户返回、前进或切换标签时,恢复之前的滚动位置和输入状态。使用或持久化视图状态。
@SceneStorage@State3. Typography & Dynamic Type
3. 排版与Dynamic Type
Impact: HIGH
影响级别:HIGH
Rule 3.1: Use Built-in Text Styles
规则3.1:使用内置文本样式
Always use semantic text styles rather than hardcoded sizes. These scale automatically with Dynamic Type.
Correct:
swift
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:
swift
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
}始终使用语义化文本样式,而非硬编码尺寸。这些样式会随Dynamic Type自动缩放。
正确示例:
swift
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)
}错误示例:
swift
VStack(alignment: .leading, spacing: 4) {
Text("Section Title")
.font(.system(size: 17, weight: .semibold)) // 不会随Dynamic Type缩放
Text("Body content")
.font(.system(size: 15)) // 不会随Dynamic Type缩放
}Rule 3.2: Support Dynamic Type Including Accessibility Sizes
规则3.2:支持含无障碍尺寸的Dynamic Type
Dynamic Type can scale text up to approximately 200% at the largest accessibility sizes. Layouts must reflow — never truncate or clip essential text.
Correct:
swift
HStack {
Image(systemName: "star")
Text("Favorites")
.font(.body)
}
// At accessibility sizes, consider using ViewThatFits or
// AnyLayout to switch from HStack to VStackUse to detect size category and adapt layouts:
@Environment(\.dynamicTypeSize)swift
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
if dynamicTypeSize.isAccessibilitySize {
VStack { content }
} else {
HStack { content }
}
}Dynamic Type可将文本缩放至最大无障碍尺寸的约200%。布局必须自动重排——切勿截断或遮挡核心文本。
正确示例:
swift
HStack {
Image(systemName: "star")
Text("Favorites")
.font(.body)
}
// 在无障碍尺寸下,可考虑使用ViewThatFits或
// AnyLayout从HStack切换为VStack使用检测尺寸类别并适配布局:
@Environment(\.dynamicTypeSize)swift
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var body: some View {
if dynamicTypeSize.isAccessibilitySize {
VStack { content }
} else {
HStack { content }
}
}Rule 3.3: Custom Fonts Must Use UIFontMetrics
规则3.3:自定义字体必须使用UIFontMetrics
If you use a custom typeface, scale it with so it responds to Dynamic Type.
UIFontMetricsCorrect:
swift
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))若使用自定义字体,需通过进行缩放,使其响应Dynamic Type。
UIFontMetrics正确示例:
swift
extension Font {
static func scaledCustom(size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font {
.custom("CustomFont-Regular", size: size, relativeTo: textStyle)
}
}
// 使用方式
Text("Hello")
.font(.scaledCustom(size: 17, relativeTo: .body))Rule 3.4: SF Pro as System Font
规则3.4:使用SF Pro作为系统字体
Use the system font (SF Pro) unless brand requirements dictate otherwise. SF Pro is optimized for legibility on Apple displays.
除非品牌要求,否则请使用系统字体(SF Pro)。SF Pro针对Apple显示器的可读性进行了优化。
Rule 3.5: Minimum 11pt Text
规则3.5:最小11pt文本
Never display text smaller than 11pt. Prefer 17pt for body text. Use the style (11pt) as the absolute minimum.
caption2切勿显示小于11pt的文本。正文文本优先使用17pt,样式(11pt)为绝对最小值。
caption2Rule 3.6: Hierarchy Through Weight and Size
规则3.6:通过字重和尺寸建立层级
Establish visual hierarchy through font weight and size. Do not rely solely on color to differentiate text levels.
通过字体粗细和尺寸建立视觉层级,切勿仅依赖颜色区分文本层级。
4. Color & Dark Mode
4. 颜色与Dark Mode
Impact: HIGH
影响级别:HIGH
Rule 4.1: Use Semantic System Colors
规则4.1:使用语义化系统颜色
Use system-provided semantic colors that automatically adapt to light and dark modes.
Correct:
swift
Text("Primary text")
.foregroundStyle(.primary) // Adapts to light/dark
Text("Secondary info")
.foregroundStyle(.secondary)
VStack { }
.background(Color(.systemBackground)) // White in light, black in darkIncorrect:
swift
Text("Primary text")
.foregroundColor(.black) // Invisible on dark backgrounds
VStack { }
.background(.white) // Blinding in Dark Mode使用系统提供的语义化颜色,这些颜色会自动适配亮色和暗色模式。
正确示例:
swift
Text("Primary text")
.foregroundStyle(.primary) // 适配亮色/暗色模式
Text("Secondary info")
.foregroundStyle(.secondary)
VStack { }
.background(Color(.systemBackground)) // 亮色模式为白色,暗色模式为黑色错误示例:
swift
Text("Primary text")
.foregroundColor(.black) // 在暗色背景上不可见
VStack { }
.background(.white) // 在Dark Mode下过于刺眼Rule 4.2: Provide Light and Dark Variants for Custom Colors
规则4.2:为自定义颜色提供亮色和暗色变体
Define custom colors in the asset catalog with both Any Appearance and Dark Appearance variants.
swift
// In Assets.xcassets, define "BrandBlue" with:
// Any Appearance: #0066CC
// Dark Appearance: #4DA3FF
Text("Brand text")
.foregroundStyle(Color("BrandBlue")) // Automatically switches在资源目录中定义自定义颜色时,需包含“任意外观”和“暗色外观”变体。
swift
// 在Assets.xcassets中定义"BrandBlue":
// 任意外观:#0066CC
// 暗色外观:#4DA3FF
Text("Brand text")
.foregroundStyle(Color("BrandBlue")) // 自动切换Rule 4.3: Never Rely on Color Alone
规则4.3:切勿仅依赖颜色传达信息
Always pair color with text, icons, or shapes to convey meaning. Approximately 8% of men have some form of color vision deficiency.
Correct:
swift
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
Text("Error: Invalid email address")
.foregroundStyle(.red)
}Incorrect:
swift
// Only color indicates the error — invisible to colorblind users
TextField("Email", text: $email)
.border(isValid ? .green : .red)始终将颜色与文本、图标或形状结合使用以传达含义。约8%的男性存在某种形式的色觉障碍。
正确示例:
swift
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
Text("Error: Invalid email address")
.foregroundStyle(.red)
}错误示例:
swift
// 仅通过颜色指示错误——色觉障碍用户无法识别
TextField("Email", text: $email)
.border(isValid ? .green : .red)Rule 4.4: 4.5:1 Contrast Ratio Minimum
规则4.4:最低4.5:1对比度
All text must meet WCAG AA contrast ratios: 4.5:1 for normal text, 3:1 for large text (18pt+ or 14pt+ bold).
所有文本必须符合WCAG AA对比度标准:普通文本为4.5:1,大文本(18pt+或14pt+粗体)为3:1。
Rule 4.5: Support Display P3 Wide Gamut
规则4.5:支持Display P3广色域
Use Display P3 color space for vibrant, accurate colors on modern iPhones. Define colors in the asset catalog with the Display P3 gamut.
在现代iPhone上使用Display P3色域以呈现鲜艳准确的颜色。在资源目录中定义颜色时选择Display P3色域。
Rule 4.6: Background Hierarchy
规则4.6:背景层级
Use the three-level background hierarchy for depth:
- — primary surface
systemBackground - — grouped content, cards
secondarySystemBackground - — elements within grouped content
tertiarySystemBackground
使用三级背景层级营造深度:
- —— 主表面
systemBackground - —— 分组内容、卡片
secondarySystemBackground - —— 分组内容内的元素
tertiarySystemBackground
Rule 4.7: One Accent Color for Interactive Elements
规则4.7:交互元素使用单一强调色
Choose a single tint/accent color for all interactive elements (buttons, links, toggles). This creates a consistent, learnable visual language.
swift
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.tint(.indigo) // All interactive elements use indigo
}
}
}为所有交互元素(按钮、链接、开关)选择单一色调/强调色,打造一致、易学习的视觉语言。
swift
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.tint(.indigo) // 所有交互元素使用靛蓝色
}
}
}5. Accessibility
5. 无障碍访问
Impact: CRITICAL
影响级别:CRITICAL
Rule 5.1: VoiceOver Labels on All Interactive Elements
规则5.1:所有交互元素添加VoiceOver标签
Every button, control, and interactive element must have a meaningful accessibility label.
Correct:
swift
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")Incorrect:
swift
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
// VoiceOver reads "cart.badge.plus" — meaningless to users每个按钮、控件和交互元素必须有意义的无障碍标签。
正确示例:
swift
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
.accessibilityLabel("Add to cart")错误示例:
swift
Button(action: addToCart) {
Image(systemName: "cart.badge.plus")
}
// VoiceOver会读取"cart.badge.plus"——对用户无意义Rule 5.2: Logical VoiceOver Navigation Order
规则5.2:合理的VoiceOver导航顺序
Ensure VoiceOver reads elements in a logical order. Use to adjust when the visual layout doesn't match the reading order.
.accessibilitySortPriority()swift
VStack {
Text("Price: $29.99")
.accessibilitySortPriority(1) // Read first
Text("Product Name")
.accessibilitySortPriority(2) // Read second
}确保VoiceOver按逻辑顺序读取元素。当视觉布局与读取顺序不匹配时,使用调整。
.accessibilitySortPriority()swift
VStack {
Text("Price: $29.99")
.accessibilitySortPriority(1) // 先读取
Text("Product Name")
.accessibilitySortPriority(2) // 后读取
}Rule 5.3: Support Bold Text
规则5.3:支持粗体文本
When the user enables Bold Text in Settings, use the dynamic type variants. SwiftUI text styles handle this automatically. Custom text must respond to .
.boldUIAccessibility.isBoldTextEnabled当用户在设置中启用粗体文本时,使用动态文本变体。SwiftUI文本样式会自动处理,自定义文本需响应。
.boldUIAccessibility.isBoldTextEnabledRule 5.4: Support Reduce Motion
规则5.4:支持减少动态效果
Disable decorative animations and parallax when Reduce Motion is enabled. Use .
@Environment(\.accessibilityReduceMotion)Correct:
swift
@Environment(\.accessibilityReduceMotion) var reduceMotion
var body: some View {
CardView()
.animation(reduceMotion ? nil : .spring(), value: isExpanded)
}当启用减少动态效果时,禁用装饰性动画和视差效果。使用。
@Environment(\.accessibilityReduceMotion)正确示例:
swift
@Environment(\.accessibilityReduceMotion) var reduceMotion
var body: some View {
CardView()
.animation(reduceMotion ? nil : .spring(), value: isExpanded)
}Rule 5.5: Support Increase Contrast
规则5.5:支持增强对比度
When the user enables Increase Contrast, ensure custom colors have higher-contrast variants. Use to detect.
@Environment(\.colorSchemeContrast)当用户启用增强对比度时,确保自定义颜色有更高对比度的变体。使用检测。
@Environment(\.colorSchemeContrast)Rule 5.6: Don't Convey Info Only by Color, Shape, or Position
规则5.6:切勿仅通过颜色、形状或位置传达信息
Information must be available through multiple channels. Pair visual indicators with text or accessibility descriptions.
信息必须通过多种渠道呈现。将视觉指示器与文本或无障碍描述结合使用。
Rule 5.7: Alternative Interactions for All Gestures
规则5.7:所有手势提供替代交互方式
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
规则5.8:支持切换控制和全键盘访问
Ensure all interactions work with Switch Control (external switches) and Full Keyboard Access (Bluetooth keyboards). Test navigation order and focus behavior.
确保所有交互可通过切换控制(外部开关)和全键盘访问(蓝牙键盘)实现。测试导航顺序和焦点行为。
6. Gestures & Input
6. 手势与输入
Impact: HIGH
影响级别:HIGH
Rule 6.1: Use Standard Gestures
规则6.1:使用标准手势
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 |
使用iOS标准手势词汇:点击、长按、滑动、捏合、旋转。用户已熟悉这些手势。
| 手势 | 标准用途 |
|---|---|
| 点击 | 主操作、选择 |
| 长按 | 上下文菜单、预览 |
| 水平滑动 | 删除、归档、返回导航 |
| 垂直滑动 | 滚动、关闭表单 |
| 捏合 | 放大/缩小 |
| 双指旋转 | 旋转内容 |
Rule 6.2: Never Override System Gestures
规则6.2:切勿覆盖系统手势
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
规则6.3:自定义手势必须可发现
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
规则6.4:支持所有输入方式
Design for touch first, but also support:
- Hardware keyboards (iPad keyboard accessories, Bluetooth keyboards)
- Assistive devices (Switch Control, head tracking)
- Pointer input (assistive touch)
优先为触摸操作设计,但同时需支持:
- 硬件键盘(iPad键盘配件、蓝牙键盘)
- 辅助设备(切换控制、头部追踪)
- 指针输入(辅助触控)
7. Components
7. 组件
Impact: HIGH
影响级别:HIGH
Rule 7.1: Button Styles
规则7.1:按钮样式
Use the built-in button styles appropriately:
- — primary call-to-action
.borderedProminent - — secondary actions
.bordered - — tertiary or inline actions
.borderless - role — red tint for delete/remove
.destructive
Correct:
swift
VStack(spacing: 16) {
Button("Purchase") { buy() }
.buttonStyle(.borderedProminent)
Button("Add to Wishlist") { wishlist() }
.buttonStyle(.bordered)
Button("Delete", role: .destructive) { delete() }
}合理使用内置按钮样式:
- —— 主要行动召唤按钮
.borderedProminent - —— 次要操作按钮
.bordered - —— 三级或行内操作按钮
.borderless - 角色 —— 删除/移除操作的红色色调
.destructive
正确示例:
swift
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
规则7.2:警告框——仅用于关键信息
Use alerts sparingly for critical information that requires a decision. Prefer 2 buttons; maximum 3. The destructive option should use role.
.destructiveCorrect:
swift
.alert("Delete Photo?", isPresented: $showAlert) {
Button("Delete", role: .destructive) { deletePhoto() }
Button("Cancel", role: .cancel) { }
} message: {
Text("This photo will be permanently removed.")
}Incorrect:
swift
// Alert for non-critical info — should be a banner or toast
.alert("Tip", isPresented: $showTip) {
Button("OK") { }
} message: {
Text("Swipe left to delete items.")
}仅在需要用户决策的关键信息场景中使用警告框。优先使用2个按钮,最多3个。破坏性选项应使用角色。
.destructive正确示例:
swift
.alert("Delete Photo?", isPresented: $showAlert) {
Button("Delete", role: .destructive) { deletePhoto() }
Button("Cancel", role: .cancel) { }
} message: {
Text("This photo will be permanently removed.")
}错误示例:
swift
// 非关键信息使用警告框——应使用横幅或提示框
.alert("Tip", isPresented: $showTip) {
Button("OK") { }
} message: {
Text("Swipe left to delete items.")
}Rule 7.3: Sheets for Scoped Tasks
规则7.3:表单用于独立任务
Present sheets for self-contained tasks. Always provide a way to dismiss (close button or swipe down). Use for half-height sheets.
.presentationDetents()swift
.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])
}使用表单呈现独立任务,始终提供关闭方式(关闭按钮或向下滑动)。使用实现半高表单。
.presentationDetents()swift
.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
规则7.4:列表——默认使用内嵌分组样式
Use the list style as the default. Support swipe actions for common operations. Minimum row height is 44pt.
.insetGroupedCorrect:
swift
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)将列表样式作为默认样式。为常见操作支持滑动操作。行高最小为44pt。
.insetGrouped正确示例:
swift
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
规则7.5:标签栏行为
- 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()
swift
TabView {
MessagesView()
.tabItem {
Label("Messages", systemImage: "message")
}
.badge(unreadCount)
}- 标签图标使用SF Symbols——选中标签使用填充变体,未选中使用轮廓变体
- 在标签内深层导航时切勿隐藏标签栏
- 使用标记重要计数
.badge()
swift
TabView {
MessagesView()
.tabItem {
Label("Messages", systemImage: "message")
}
.badge(unreadCount)
}Rule 7.6: Search
规则7.6:搜索
Place search using . Provide search suggestions and support recent searches.
.searchable()swift
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)
}
}
}使用添加搜索功能,提供搜索建议并支持最近搜索。
.searchable()swift
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
规则7.7:上下文菜单
Use context menus (long press) for secondary actions. Never use a context menu as the only way to access an action.
swift
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")
}
}使用上下文菜单(长按)触发次要操作,切勿将上下文菜单作为操作的唯一访问方式。
swift
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
规则7.8:进度指示器
- Determinate () for operations with known duration
ProgressView(value:total:) - Indeterminate () for unknown duration
ProgressView() - Never block the entire screen with a spinner
- 确定型进度指示器()用于已知时长的操作
ProgressView(value:total:) - 不确定型进度指示器()用于未知时长的操作
ProgressView() - 切勿使用全屏阻塞式加载指示器
8. Patterns
8. 模式
Impact: MEDIUM
影响级别:MEDIUM
Rule 8.1: Onboarding — Max 3 Pages, Skippable
规则8.1:引导页——最多3页,可跳过
Keep onboarding to 3 or fewer pages. Always provide a skip option. Defer sign-in until the user needs authenticated features.
swift
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()
}引导页不超过3页,始终提供跳过选项。在用户需要认证功能前,延迟要求登录。
swift
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
规则8.2:加载——骨架屏,无阻塞式加载器
Use skeleton/placeholder views that match the layout of the content being loaded. Never show a full-screen blocking spinner.
Correct:
swift
if isLoading {
ForEach(0..<5) { _ in
SkeletonRow() // Placeholder matching final row layout
.redacted(reason: .placeholder)
}
} else {
ForEach(items) { item in
ItemRow(item: item)
}
}Incorrect:
swift
if isLoading {
ProgressView("Loading...") // Blocks the entire view
} else {
List(items) { item in ItemRow(item: item) }
}使用与加载内容布局匹配的骨架/占位视图,切勿显示全屏阻塞式加载指示器。
正确示例:
swift
if isLoading {
ForEach(0..<5) { _ in
SkeletonRow() // 与最终行布局匹配的占位符
.redacted(reason: .placeholder)
}
} else {
ForEach(items) { item in
ItemRow(item: item)
}
}错误示例:
swift
if isLoading {
ProgressView("Loading...") // 阻塞整个视图
} else {
List(items) { item in ItemRow(item: item) }
}Rule 8.3: Launch Screen — Match First Screen
规则8.3:启动屏——匹配首屏
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.
启动故事板必须与应用的初始屏幕视觉匹配,无闪屏logo或品牌屏幕,营造即时启动的感知。
Rule 8.4: Modality — Use Sparingly
规则8.4:模态视图——谨慎使用
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
规则8.5:通知——仅高价值内容
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
规则8.6:设置位置
- 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
- 常用设置: 应用内设置页面,可通过个人资料或齿轮图标访问
- 隐私/权限设置: 通过URL方案跳转到系统设置应用,切勿在应用内复制系统级控件
Rule 8.7: Feedback — Visual + Haptic
规则8.7:反馈——视觉+触觉
Provide immediate feedback for every user action:
- Visual state change (button highlight, animation)
- Haptic feedback for significant actions using ,
UIImpactFeedbackGenerator, orUINotificationFeedbackGeneratorUISelectionFeedbackGenerator
swift
Button("Complete") {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
completeTask()
}为每个用户操作提供即时反馈:
- 视觉状态变化(按钮高亮、动画)
- 使用、
UIImpactFeedbackGenerator或UINotificationFeedbackGenerator为重要操作提供触觉反馈UISelectionFeedbackGenerator
swift
Button("Complete") {
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
completeTask()
}9. Privacy & Permissions
9. 隐私与权限
Impact: HIGH
影响级别:HIGH
Rule 9.1: Request Permissions in Context
规则9.1:在上下文场景中请求权限
Request a permission at the moment the user takes an action that needs it — never at app launch.
Correct:
swift
Button("Take Photo") {
// Request camera permission only when the user taps this button
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted { showCamera = true }
}
}Incorrect:
swift
// 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 }
}仅在用户执行需要权限的操作时请求权限——切勿在应用启动时请求。
正确示例:
swift
Button("Take Photo") {
// 仅当用户点击此按钮时请求相机权限
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted { showCamera = true }
}
}错误示例:
swift
// 在AppDelegate.didFinishLaunching中请求——时机过早,无上下文
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
规则9.2:系统提示前先说明
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.
swift
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()
}
}在触发系统权限对话框前,显示自定义说明屏幕。系统对话框仅显示一次——若用户拒绝,应用需引导至设置。
swift
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
规则9.3:支持通过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.
若应用提供第三方登录(Google、Facebook),则必须同时提供通过Apple登录,并将其作为第一个选项。
Rule 9.4: Don't Require Accounts Unless Necessary
规则9.4:非必要不要求账号
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
规则9.5:应用跟踪透明度
If you track users across apps or websites, display the ATT prompt. Respect denial — do not degrade the experience for users who opt out.
若跨应用或网站跟踪用户,需显示ATT提示。尊重用户的拒绝选择——切勿降低拒绝用户的体验。
Rule 9.6: Location Button for One-Time Access
规则9.6:一次性位置访问使用LocationButton
Use for actions that need location once without requesting ongoing permission.
LocationButtonswift
LocationButton(.currentLocation) {
fetchNearbyStores()
}
.labelStyle(.titleAndIcon)使用实现仅需一次位置访问的操作,无需请求持续权限。
LocationButtonswift
LocationButton(.currentLocation) {
fetchNearbyStores()
}
.labelStyle(.titleAndIcon)10. System Integration
10. 系统集成
Impact: MEDIUM
影响级别:MEDIUM
Rule 10.1: Widgets for Glanceable Data
规则10.1:Widget用于快速查看数据
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.
使用WidgetKit提供小组件,供用户快速查看频繁关注的信息。小组件不可交互(除点击打开应用外),因此需显示最有用的快照。
Rule 10.2: App Shortcuts for Key Actions
规则10.2:应用快捷方式用于关键操作
Define App Shortcuts so users can trigger key actions from Siri, Spotlight, and the Shortcuts app.
swift
struct MyAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: StartWorkoutIntent(),
phrases: ["Start a workout in \(.applicationName)"],
shortTitle: "Start Workout",
systemImageName: "figure.run"
)
}
}定义应用快捷方式,以便用户从Siri、Spotlight和快捷指令应用触发关键操作。
swift
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
规则10.3:Spotlight索引
Index app content with so users can find it from Spotlight search.
CSSearchableItem使用为应用内容建立索引,以便用户通过Spotlight搜索找到。
CSSearchableItemRule 10.4: Share Sheet Integration
规则10.4:共享表单集成
Support the system share sheet for content that users might want to send elsewhere. Implement or use in SwiftUI.
UIActivityItemSourceShareLinkswift
ShareLink(item: article.url) {
Label("Share", systemImage: "square.and.arrow.up")
}支持系统共享表单,供用户分享内容。在SwiftUI中实现或使用。
UIActivityItemSourceShareLinkswift
ShareLink(item: article.url) {
Label("Share", systemImage: "square.and.arrow.up")
}Rule 10.5: Live Activities
规则10.5:实时活动
Use Live Activities and the Dynamic Island for real-time, time-bound events (delivery tracking, sports scores, workouts).
使用实时活动和Dynamic Island显示实时、有时间限制的事件(配送跟踪、体育比分、健身)。
Rule 10.6: Handle Interruptions Gracefully
规则10.6:优雅处理中断
Save state and pause gracefully when interrupted by:
- Phone calls
- Siri invocations
- Notifications
- App switcher
- FaceTime SharePlay
Use to detect transitions:
scenePhaseswift
@Environment(\.scenePhase) var scenePhase
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
case .active: resumeActivity()
case .inactive: pauseActivity()
case .background: saveState()
@unknown default: break
}
}当被以下情况中断时,保存状态并优雅暂停:
- 电话
- Siri调用
- 通知
- 应用切换器
- FaceTime SharePlay
使用检测状态转换:
scenePhaseswift
@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) | | Bottom tab bar, SF Symbols |
| Hierarchical drill-down | | Large title on root, inline on children |
| Self-contained task | | Swipe to dismiss, cancel/done buttons |
| Critical decision | | 2 buttons preferred, max 3 |
| Secondary actions | | Long press; must also be accessible elsewhere |
| Scrolling content | | 44pt min row, swipe actions |
| Text input | | Label above, validation below |
| Selection (few options) | | Segmented for 2-5, wheel for many |
| Selection (on/off) | | Aligned right in a list row |
| Search | | Suggestions, recent searches |
| Progress (known) | | Show percentage or time remaining |
| Progress (unknown) | | Inline, never full-screen blocking |
| One-time location | | No persistent permission needed |
| Sharing content | | System share sheet |
| Haptic feedback | | |
| Destructive action | | Red tint, confirm via alert |
| 需求 | 组件 | 说明 |
|---|---|---|
| 顶级分区(3-5个) | | 底部标签栏,使用SF Symbols |
| 层级钻取 | | 根视图使用大标题,子视图使用行内标题 |
| 独立任务 | | 可滑动关闭,提供取消/完成按钮 |
| 关键决策 | | 优先2个按钮,最多3个 |
| 次要操作 | | 长按触发;必须同时提供其他访问方式 |
| 滚动内容 | 搭配 | 最小行高44pt,支持滑动操作 |
| 文本输入 | | 标签在上,验证信息在下 |
| 选择(少量选项) | | 2-5个选项使用分段式,多选项使用滚轮式 |
| 选择(开/关) | | 在列表行中右对齐 |
| 搜索 | | 支持建议和最近搜索 |
| 进度(已知时长) | | 显示百分比或剩余时间 |
| 进度(未知时长) | | 行内显示,切勿全屏阻塞 |
| 一次性位置访问 | | 无需持续权限 |
| 内容分享 | | 系统共享表单 |
| 触觉反馈 | | |
| 破坏性操作 | | 红色色调,通过警告框确认 |
Evaluation Checklist
评估清单
Use this checklist to audit an iPhone app for HIG compliance:
使用此清单审核iPhone应用的HIG合规性:
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
- 所有触摸目标至少为44x44pt
- 无内容被状态栏、Dynamic Island或主屏幕指示器遮挡
- 主操作位于屏幕下半部分(拇指可达区)
- 布局从iPhone SE到Pro Max均可适配,无断裂
- 间距对齐到8pt网格
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
- 标签栏用于3-5个顶级分区
- 无汉堡/抽屉菜单
- 主视图使用大标题
- 从左边缘滑动返回导航在全应用可用
- 切换标签时保留状态
Typography
排版
- All text uses built-in text styles or -scaled custom fonts
UIFontMetrics - Dynamic Type is supported up to accessibility sizes
- Layouts reflow at large text sizes (no truncation of essential text)
- Minimum text size is 11pt
- 所有文本使用内置文本样式或缩放的自定义字体
UIFontMetrics - 支持Dynamic Type直至无障碍尺寸
- 大文本尺寸下布局自动重排(无核心文本截断)
- 最小文本尺寸为11pt
Color & Dark Mode
颜色与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
- 应用使用语义化系统颜色或提供亮色/暗色资源变体
- Dark Mode设计合理(非简单反转)
- 无仅通过颜色传达的信息
- 文本对比度符合4.5:1(普通文本)或3:1(大文本)
- 交互元素使用单一强调色
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
- VoiceOver按逻辑顺序读取所有屏幕,标签有意义
- 尊重粗体文本偏好
- 减少动态效果时禁用装饰性动画
- 自定义颜色有增强对比度变体
- 所有手势有替代访问路径
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 role
.destructive
- 警告框仅用于关键决策
- 表单提供关闭路径(按钮和/或滑动)
- 列表行高至少44pt
- 导航过程中标签栏始终可见
- 破坏性按钮使用角色
.destructive
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
- 在上下文场景中请求权限,而非启动时
- 每个系统权限对话框前显示自定义说明
- 与其他登录提供商一同提供通过Apple登录
- 基础功能无需账号即可使用
- 若跟踪用户则显示ATT提示,并尊重拒绝选择
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
- 小组件显示可快速查看的最新信息
- 应用内容已建立Spotlight索引
- 可分享内容支持共享表单
- 应用可优雅处理中断(电话、后台、Siri)
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 — Usingon content views causes text and buttons to disappear under the notch, Dynamic Island, or home indicator.
.ignoresSafeArea() -
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.
以下是违反iOS人机界面指南的常见错误,切勿执行:
-
汉堡菜单 —— 使用标签栏。汉堡菜单会隐藏导航,导致功能可发现性降低多达50%。
-
自定义返回按钮破坏滑动返回 —— 若替换返回按钮,需确保通过仍可使用从左边缘滑动返回的手势。
NavigationStack -
全屏阻塞式加载指示器 —— 使用骨架屏或行内进度指示器。阻塞式加载器会让应用看起来像冻结了一样。
-
带logo的闪屏 —— 启动屏必须与应用首屏镜像。品牌延迟会显得不自然。
-
启动时请求所有权限 —— 首次启动时请求相机、位置、通知和联系人权限,绝大多数会被拒绝。
-
硬编码字体尺寸 —— 使用文本样式。硬编码尺寸会忽略Dynamic Type和无障碍偏好,导致数百万用户无法正常使用应用。
-
仅通过颜色指示状态 —— 用红/绿表示有效/无效会排除色觉障碍用户,始终搭配图标或文本。
-
非关键信息使用警告框 —— 警告框会打断流程并需要用户关闭,对于提示和非关键信息,请使用横幅、提示框或行内消息。
-
推送时隐藏标签栏 —— 在标签内导航时,标签栏应始终可见。隐藏标签栏会使用户迷失方向。
-
忽略安全区域 —— 在内容视图上使用会导致文本和按钮消失在刘海、Dynamic Island或主屏幕指示器下方。
.ignoresSafeArea() -
无法关闭的模态视图 —— 每个模态视图必须有清晰的关闭路径(关闭按钮、取消、向下滑动)。将用户困在模态视图中是不友好的。
-
自定义手势无替代方式 —— 三指滑动撤销对许多人来说无法使用,同时提供可见按钮或菜单项。
-
过小的触摸目标 —— 小于44pt的按钮和链接会导致误触,尤其是在列表和工具栏中。
-
堆叠模态视图 —— 在模态视图上再呈现模态视图会造成导航混乱,应在单个模态视图内使用导航。
-
Dark Mode作为事后补充 —— 使用硬编码颜色会导致应用在Dark Mode或亮色模式中崩溃,始终使用语义化颜色。