ios-design-guidelines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

iOS 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
safeAreaLayoutGuide
.
Correct:
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
.ignoresSafeArea()
only for background fills, images, or decorative elements — never for text or interactive controls.
切勿将交互或核心内容放置在状态栏、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
ViewThatFits
or
GeometryReader
for adaptive layouts.

除非应用为特定任务设计(如相机应用),否则需支持横屏模式。使用
ViewThatFits
GeometryReader
实现自适应布局。

2. 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
.navigationBarTitleDisplayMode(.large)
for top-level views. Titles transition to inline (
.inline
) when the user scrolls.
Correct:
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
NavigationStack
(not the deprecated
NavigationView
) for drill-down content. Use
NavigationPath
for programmatic navigation.
Correct:
swift
NavigationStack(path: $path) {
    List(items) { item in
        NavigationLink(value: item) {
            ItemRow(item: item)
        }
    }
    .navigationDestination(for: Item.self) { item in
        ItemDetail(item: item)
    }
}
使用
NavigationStack
(而非已弃用的
NavigationView
)处理钻取式内容,使用
NavigationPath
实现程序化导航。
正确示例:
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
@SceneStorage
or
@State
to persist view state.

当用户返回、前进或切换标签时,恢复之前的滚动位置和输入状态。使用
@SceneStorage
@State
持久化视图状态。

3. 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 VStack
Use
@Environment(\.dynamicTypeSize)
to detect size category and adapt layouts:
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
UIFontMetrics
so it responds to Dynamic Type.
Correct:
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))
若使用自定义字体,需通过
UIFontMetrics
进行缩放,使其响应Dynamic Type。
正确示例:
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
caption2
style (11pt) as the absolute minimum.
切勿显示小于11pt的文本。正文文本优先使用17pt,
caption2
样式(11pt)为绝对最小值。

Rule 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 dark
Incorrect:
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:
  • systemBackground
    — primary surface
  • secondarySystemBackground
    — grouped content, cards
  • tertiarySystemBackground
    — elements within grouped content
使用三级背景层级营造深度:
  • 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
.accessibilitySortPriority()
to adjust when the visual layout doesn't match the reading order.
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
.bold
dynamic type variants. SwiftUI text styles handle this automatically. Custom text must respond to
UIAccessibility.isBoldTextEnabled
.
当用户在设置中启用粗体文本时,使用
.bold
动态文本变体。SwiftUI文本样式会自动处理,自定义文本需响应
UIAccessibility.isBoldTextEnabled

Rule 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
@Environment(\.colorSchemeContrast)
to detect.
当用户启用增强对比度时,确保自定义颜色有更高对比度的变体。使用
@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.
GestureStandard Use
TapPrimary action, selection
Long pressContext menu, preview
Swipe horizontalDelete, archive, navigate back
Swipe verticalScroll, dismiss sheet
PinchZoom in/out
Two-finger rotateRotate 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:
  • .borderedProminent
    — primary call-to-action
  • .bordered
    — secondary actions
  • .borderless
    — tertiary or inline actions
  • .destructive
    role — red tint for delete/remove
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
.destructive
role.
Correct:
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
.presentationDetents()
for half-height sheets.
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
.insetGrouped
list style as the default. Support swipe actions for common operations. Minimum row height is 44pt.
Correct:
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)
.insetGrouped
列表样式作为默认样式。为常见操作支持滑动操作。行高最小为44pt。
正确示例:
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
.searchable()
. Provide search suggestions and support recent searches.
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 (
    ProgressView(value:total:)
    ) for operations with known duration
  • Indeterminate (
    ProgressView()
    ) for unknown duration
  • 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
    ,
    UINotificationFeedbackGenerator
    , or
    UISelectionFeedbackGenerator
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
LocationButton
for actions that need location once without requesting ongoing permission.
swift
LocationButton(.currentLocation) {
    fetchNearbyStores()
}
.labelStyle(.titleAndIcon)

使用
LocationButton
实现仅需一次位置访问的操作,无需请求持续权限。
swift
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
CSSearchableItem
so users can find it from Spotlight search.
使用
CSSearchableItem
为应用内容建立索引,以便用户通过Spotlight搜索找到。

Rule 10.4: Share Sheet Integration

规则10.4:共享表单集成

Support the system share sheet for content that users might want to send elsewhere. Implement
UIActivityItemSource
or use
ShareLink
in SwiftUI.
swift
ShareLink(item: article.url) {
    Label("Share", systemImage: "square.and.arrow.up")
}
支持系统共享表单,供用户分享内容。在SwiftUI中实现
UIActivityItemSource
或使用
ShareLink
swift
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
scenePhase
to detect transitions:
swift
@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
使用
scenePhase
检测状态转换:
swift
@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

快速参考

NeedComponentNotes
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

需求组件说明
顶级分区(3-5个)
TabView
搭配
.tabItem
底部标签栏,使用SF Symbols
层级钻取
NavigationStack
根视图使用大标题,子视图使用行内标题
独立任务
.sheet
可滑动关闭,提供取消/完成按钮
关键决策
.alert
优先2个按钮,最多3个
次要操作
.contextMenu
长按触发;必须同时提供其他访问方式
滚动内容搭配
.insetGrouped
List
最小行高44pt,支持滑动操作
文本输入
TextField
/
TextEditor
标签在上,验证信息在下
选择(少量选项)
Picker
2-5个选项使用分段式,多选项使用滚轮式
选择(开/关)
Toggle
在列表行中右对齐
搜索
.searchable
支持建议和最近搜索
进度(已知时长)
ProgressView(value:total:)
显示百分比或剩余时间
进度(未知时长)
ProgressView()
行内显示,切勿全屏阻塞
一次性位置访问
LocationButton
无需持续权限
内容分享
ShareLink
系统共享表单
触觉反馈
UIImpactFeedbackGenerator
.light
.medium
.heavy
破坏性操作
Button(role: .destructive)
红色色调,通过警告框确认

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
    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
  • 所有文本使用内置文本样式或
    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
    .destructive
    role
  • 警告框仅用于关键决策
  • 表单提供关闭路径(按钮和/或滑动)
  • 列表行高至少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:
  1. Hamburger menus — Use a tab bar. Hamburger menus hide navigation and reduce feature discoverability by up to 50%.
  2. Custom back buttons that break swipe-back — If you replace the back button, ensure the swipe-from-left-edge gesture still works via
    NavigationStack
    .
  3. Full-screen blocking spinners — Use skeleton views or inline progress indicators. Blocking spinners make the app feel frozen.
  4. Splash screens with logos — The launch screen must mirror the first screen of the app. Branding delays feel artificial.
  5. Requesting all permissions at launch — Asking for camera, location, notifications, and contacts on first launch guarantees most will be denied.
  6. Hardcoded font sizes — Use text styles. Hardcoded sizes ignore Dynamic Type and accessibility preferences, breaking the app for millions of users.
  7. Using only color to indicate state — Red/green for valid/invalid excludes colorblind users. Always pair with icons or text.
  8. Alerts for non-critical information — Alerts interrupt flow and require dismissal. Use banners, toasts, or inline messages for tips and non-critical information.
  9. Hiding the tab bar on push — Tab bars should remain visible throughout navigation within a tab. Hiding them disorients users.
  10. Ignoring safe areas — Using
    .ignoresSafeArea()
    on content views causes text and buttons to disappear under the notch, Dynamic Island, or home indicator.
  11. Non-dismissable modals — Every modal must have a clear dismiss path (close button, cancel, swipe down). Trapping users in a modal is hostile.
  12. Custom gestures without alternatives — A three-finger swipe for undo is unusable for many people. Provide a visible button or menu item as well.
  13. Tiny touch targets — Buttons and links smaller than 44pt cause mis-taps, especially in lists and toolbars.
  14. 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.
  15. 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人机界面指南的常见错误,切勿执行:
  1. 汉堡菜单 —— 使用标签栏。汉堡菜单会隐藏导航,导致功能可发现性降低多达50%。
  2. 自定义返回按钮破坏滑动返回 —— 若替换返回按钮,需确保通过
    NavigationStack
    仍可使用从左边缘滑动返回的手势。
  3. 全屏阻塞式加载指示器 —— 使用骨架屏或行内进度指示器。阻塞式加载器会让应用看起来像冻结了一样。
  4. 带logo的闪屏 —— 启动屏必须与应用首屏镜像。品牌延迟会显得不自然。
  5. 启动时请求所有权限 —— 首次启动时请求相机、位置、通知和联系人权限,绝大多数会被拒绝。
  6. 硬编码字体尺寸 —— 使用文本样式。硬编码尺寸会忽略Dynamic Type和无障碍偏好,导致数百万用户无法正常使用应用。
  7. 仅通过颜色指示状态 —— 用红/绿表示有效/无效会排除色觉障碍用户,始终搭配图标或文本。
  8. 非关键信息使用警告框 —— 警告框会打断流程并需要用户关闭,对于提示和非关键信息,请使用横幅、提示框或行内消息。
  9. 推送时隐藏标签栏 —— 在标签内导航时,标签栏应始终可见。隐藏标签栏会使用户迷失方向。
  10. 忽略安全区域 —— 在内容视图上使用
    .ignoresSafeArea()
    会导致文本和按钮消失在刘海、Dynamic Island或主屏幕指示器下方。
  11. 无法关闭的模态视图 —— 每个模态视图必须有清晰的关闭路径(关闭按钮、取消、向下滑动)。将用户困在模态视图中是不友好的。
  12. 自定义手势无替代方式 —— 三指滑动撤销对许多人来说无法使用,同时提供可见按钮或菜单项。
  13. 过小的触摸目标 —— 小于44pt的按钮和链接会导致误触,尤其是在列表和工具栏中。
  14. 堆叠模态视图 —— 在模态视图上再呈现模态视图会造成导航混乱,应在单个模态视图内使用导航。
  15. Dark Mode作为事后补充 —— 使用硬编码颜色会导致应用在Dark Mode或亮色模式中崩溃,始终使用语义化颜色。