watchos-design-guidelines by ehmo/platform-design-skills
npx skills add https://github.com/ehmo/platform-design-skills --skill watchos-design-guidelinesApple Watch 是一款佩戴于腕部的个人化、可快速浏览的设备。交互以秒为单位衡量,而非分钟。每个设计决策都必须优先考虑理解速度和交互简洁性。
这是 watchOS 的核心约束。如果用户在抬起手腕的 2 秒内无法获取关键信息,则设计失败。
| 设备 | 屏幕宽度 | 屏幕高度 | 圆角半径 |
|---|---|---|---|
| 41mm (Series 9) | 176px | 215px | 36px |
| 45mm (Series 9) | 198px | 242px | 39px |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 42mm (Series 10) | 180px | 220px | 37px |
| 46mm (Series 10) | 205px | 251px | 40px |
| 49mm (Ultra 2) | 205px | 251px | 40px |
数码表冠是用于滚动和精确值选择的主要物理输入方式。它提供触觉反馈,应感觉有目的性。
正确 — 带有触觉定位感的表冠绑定:
struct VolumePickerView: View {
@State private var volume: Double = 0.5
var body: some View {
VStack {
Text("\(Int(volume * 100))%")
.font(.title.bold())
Image(systemName: "speaker.wave.3")
}
.focusable()
.digitalCrownRotation(
$volume,
from: 0.0,
through: 1.0,
by: 0.05,
sensitivity: .medium,
isContinuous: false,
isHapticFeedbackEnabled: true
)
}
}
错误 — 忽略表冠并强制仅通过触摸交互:
struct VolumePickerView: View {
@State private var volume: Double = 0.5
var body: some View {
Slider(value: $volume)
// 没有 .digitalCrownRotation — 表冠输入被忽略
// 用户必须仅使用触摸,这在手表上不精确且令人沮丧
}
}
手表导航必须浅层且可预测。用户不应感到迷失或无法返回已知状态。
TabView 处理顶级部分(最多 5 个标签页)。在标签页之间水平滑动。每个标签页是一个独立的功能区域。NavigationStack 进行分层深入导航。将层级限制在最多 2-3 层。每个被推送的视图都必须有一个返回按钮(由系统自动提供)。| 模式 | 使用场景 | 手势 |
|---|---|---|
| 垂直滚动 | 单个视图内的长篇幅内容 | 数码表冠 / 上下滑动 |
| TabView(水平页面) | 顶级应用部分 | 左右滑动 |
| NavigationStack(推送/弹出) | 分层深入导航 | 点击推送,向右滑动或点击返回按钮弹出 |
| 模态表单 | 确认、专注输入 | 以编程方式呈现,通过按钮或向下滑动手势关闭 |
复杂功能是手表应用最显眼的表面。它们位于表盘上,无需启动应用即可提供一目了然的数据。
accessoryCircular、accessoryCorner 和 accessoryRectangular(WidgetKit,watchOS 9+)。TimelineProvider 更新复杂功能。当数据可预测时(例如,下一个日历事件、天气预报),提供未来的时间线条目。保持数据新鲜 — 过时的复杂功能会削弱信任。正确 — 用于 accessoryCircular 复杂功能的 WidgetKit TimelineProvider:
struct StepCountProvider: TimelineProvider {
func placeholder(in context: Context) -> StepEntry {
StepEntry(date: Date(), steps: 5000)
}
func getSnapshot(in context: Context, completion: @escaping (StepEntry) -> Void) {
completion(StepEntry(date: Date(), steps: HealthStore.shared.todaySteps))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<StepEntry>) -> Void) {
let entry = StepEntry(date: Date(), steps: HealthStore.shared.todaySteps)
// 15 分钟后刷新
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
struct StepCountComplicationView: View {
let entry: StepEntry
var body: some View {
Gauge(value: Double(entry.steps), in: 0...10000) {
Image(systemName: "figure.walk")
} currentValueLabel: {
Text("\(entry.steps / 1000)k")
}
.gaugeStyle(.accessoryCircular)
}
}
使用 WidgetFamily 值:
| 系列 | 形状 | 典型内容 |
|---|---|---|
accessoryCircular | 小圆形 | 单一数值、图标或仪表 |
accessoryCorner | 弯曲,顶部角落 | 带标签的仪表,或带图标的文本 |
accessoryRectangular | 宽矩形 | 多行文本、图表或详细视图 |
accessoryInline | 文本行 | 短标签或数值 |
当用户手腕放下时,watchOS 进入始终显示状态,显示当前应用的变暗版本。这必须有意识地处理。
.everyMinute 计划的 TimelineView。体能训练和健康应用有独特的要求:长时间会话、实时指标和身体感知功能。
手表通知必须简短且可操作。用户抬起手腕的时间只有片刻。
.notification 用于标准提醒,.directionUp 用于积极事件,.directionDown 用于消极事件,.success/.failure/.retry 用于结果。| 触觉 | 使用场景 |
|---|---|
.notification | 一般提醒 |
.directionUp | 积极事件(目标达成、股票上涨) |
.directionDown | 消极事件(股票下跌、天气警告) |
.success | 操作成功完成 |
.failure | 操作失败 |
.retry | 重试提示 |
.start | 活动开始 |
.stop | 活动结束 |
.click | 离散选择(表冠定位点、选择器) |
Apple Watch 支持 VoiceOver 和其他辅助技术。复杂功能和应用 UI 必须是无障碍的。
.accessibilityLabel()。.accessibilityValue() 和 .accessibilityHint()。@Environment(\.accessibilityReduceMotion)。@Environment(\.legibilityWeight)。@Environment(\.colorSchemeContrast) 检测用户的偏好。正确:
Button(action: startWorkout) {
Image(systemName: "play.fill")
}
.accessibilityLabel("Start workout")
错误:
Button(action: startWorkout) {
Image(systemName: "play.fill")
}
// VoiceOver 读出 "play" — 不清楚此操作执行什么
在审查 watchOS 设计或实现时使用此清单。
.accessibilityValue() / .accessibilityHint() 提供无障碍值和提示@Environment(\.accessibilityReduceMotion))@Environment(\.legibilityWeight))每周安装量
220
仓库
GitHub 星标数
283
首次出现
2026 年 2 月 1 日
安全审计
安装于
codex187
opencode184
claude-code183
gemini-cli178
github-copilot164
cursor154
Apple Watch is a personal, glanceable device worn on the wrist. Interactions are measured in seconds, not minutes. Every design decision must prioritize speed of comprehension and brevity of interaction.
The defining constraint of watchOS. If a user cannot extract the key information within 2 seconds of raising their wrist, the design has failed.
| Device | Screen Width | Screen Height | Corner Radius |
|---|---|---|---|
| 41mm (Series 9) | 176px | 215px | 36px |
| 45mm (Series 9) | 198px | 242px | 39px |
| 42mm (Series 10) | 180px | 220px | 37px |
| 46mm (Series 10) | 205px | 251px | 40px |
| 49mm (Ultra 2) | 205px | 251px | 40px |
The Digital Crown is the primary physical input for scrolling and precise value selection. It provides haptic feedback and should feel purposeful.
Correct — Crown binding with haptic detents:
struct VolumePickerView: View {
@State private var volume: Double = 0.5
var body: some View {
VStack {
Text("\(Int(volume * 100))%")
.font(.title.bold())
Image(systemName: "speaker.wave.3")
}
.focusable()
.digitalCrownRotation(
$volume,
from: 0.0,
through: 1.0,
by: 0.05,
sensitivity: .medium,
isContinuous: false,
isHapticFeedbackEnabled: true
)
}
}
Incorrect — ignoring the Crown and forcing touch-only interaction:
struct VolumePickerView: View {
@State private var volume: Double = 0.5
var body: some View {
Slider(value: $volume)
// No .digitalCrownRotation — Crown input is ignored
// Users must use touch-only, which is imprecise and frustrating on Watch
}
}
Watch navigation must be shallow and predictable. Users should never feel lost or unable to return to a known state.
TabView for top-level sections (max 5 tabs). Swipe horizontally between tabs. Each tab is a distinct functional area.NavigationStack for hierarchical drill-down. Limit hierarchy to 2-3 levels maximum. Every pushed view must have a back button (provided automatically by the system).| Pattern | Use Case | Gesture |
|---|---|---|
| Vertical scroll | Long-form content within a single view | Digital Crown / swipe up-down |
| TabView (horizontal pages) | Top-level app sections | Swipe left-right |
| NavigationStack (push/pop) | Hierarchical drill-down | Tap to push, swipe right or back button to pop |
| Modal sheet | Confirmation, focused input | Presented programmatically, dismiss via button or swipe down |
Complications are the most visible surface of a Watch app. They live on the watch face and provide at-a-glance data without launching the app.
accessoryCircular, accessoryCorner, and accessoryRectangular (WidgetKit, watchOS 9+).TimelineProvider. Provide future timeline entries when data is predictable (e.g., next calendar event, weather forecast). Keep data fresh -- stale complications erode trust.Correct — WidgetKit TimelineProvider for an accessoryCircular complication:
struct StepCountProvider: TimelineProvider {
func placeholder(in context: Context) -> StepEntry {
StepEntry(date: Date(), steps: 5000)
}
func getSnapshot(in context: Context, completion: @escaping (StepEntry) -> Void) {
completion(StepEntry(date: Date(), steps: HealthStore.shared.todaySteps))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<StepEntry>) -> Void) {
let entry = StepEntry(date: Date(), steps: HealthStore.shared.todaySteps)
// Refresh in 15 minutes
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
struct StepCountComplicationView: View {
let entry: StepEntry
var body: some View {
Gauge(value: Double(entry.steps), in: 0...10000) {
Image(systemName: "figure.walk")
} currentValueLabel: {
Text("\(entry.steps / 1000)k")
}
.gaugeStyle(.accessoryCircular)
}
}
Use WidgetFamily values:
| Family | Shape | Typical Content |
|---|---|---|
accessoryCircular | Small circle | Single value, icon, or gauge |
accessoryCorner | Curved, top corners | Gauge with label, or text with icon |
accessoryRectangular | Wide rectangle | Multi-line text, chart, or detailed view |
accessoryInline | Text row | Short label or value |
When the user's wrist is down, watchOS enters an Always On state showing a dimmed version of the current app. This must be handled intentionally.
TimelineView with a .everyMinute schedule for time-sensitive content.Workout and health apps have unique requirements: extended sessions, live metrics, and body-awareness features.
Watch notifications must be brief and actionable. The user's wrist is raised for only a moment.
.notification for standard alerts, .directionUp for positive events, .directionDown for negative events, .success/.failure/.retry for outcomes.| Haptic | Use Case |
|---|---|
.notification | General alerts |
.directionUp | Positive event (goal reached, stock up) |
.directionDown | Negative event (stock down, weather warning) |
.success | Action completed successfully |
.failure | Action failed |
.retry | Try again prompt |
Apple Watch supports VoiceOver and other assistive technologies. Complications and app UI must be accessible.
.accessibilityLabel() on image-only buttons..accessibilityValue() and .accessibilityHint().@Environment(\.accessibilityReduceMotion).@Environment(\.legibilityWeight).@Environment(\.colorSchemeContrast) to detect the user's preference.Correct:
Button(action: startWorkout) {
Image(systemName: "play.fill")
}
.accessibilityLabel("Start workout")
Incorrect:
Button(action: startWorkout) {
Image(systemName: "play.fill")
}
// VoiceOver reads "play" — not clear what action this performs
Use this checklist when reviewing a watchOS design or implementation.
.accessibilityValue() / .accessibilityHint()@Environment(\.accessibilityReduceMotion))@Environment(\.legibilityWeight))Weekly Installs
220
Repository
GitHub Stars
283
First Seen
Feb 1, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex187
opencode184
claude-code183
gemini-cli178
github-copilot164
cursor154
前端设计技能指南:避免AI垃圾美学,打造独特生产级界面
39,800 周安装
.start | Activity beginning |
.stop | Activity ending |
.click | Discrete selection (Crown detent, picker) |