ios-taste

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

iOS Taste

iOS 质感设计

Taste doesn't start at the pixel level. It starts at "who is this person and what do they need?" The visual refinement is the LAST step. The first step is understanding the user's world deeply enough that the interface design feels inevitable — like it couldn't have been designed any other way.
Your default mode skips straight to layout. It produces technically correct SwiftUI that looks generic because it was never grounded in a real person's needs. This skill changes the order of operations: think like a designer first, then write code.
质感并非始于像素层面,而是始于“这个用户是谁,他们需要什么?”视觉优化是最后一步。第一步是深入理解用户的使用场景,让界面设计显得理所当然——仿佛它不可能以其他方式设计。
你的默认模式会直接跳到布局阶段,生成技术上正确但看起来千篇一律的SwiftUI代码,因为它从未基于真实用户的需求。此技能改变了操作顺序:先像设计师一样思考,再编写代码。

Phase 0: The 0.5-Second Test (ORIENT before everything)

第0阶段:0.5秒测试(先定位,再做其他)

Before designing anything, answer ONE question:
What does the user SEE in the first half-second — before they read a single word?
This is not about content. It's about the SHAPE of the screen. Close your eyes and picture it. What dominates?
  • A ring at 40% of screen height? → Fitness dashboard
  • A gradient card with bold white text? → Music/travel/content
  • A large number floating in space? → Finance/health metric
  • A grid of thumbnails? → Photo/recipe/shopping collection
  • A clean form with generous space? → Settings/profile
If your answer is "a List with rows of text" → STOP. That's a spreadsheet, not an app. Go back and find the visual shape.
Write the 0.5-second answer as the FIRST line of the experience brief:
swift
// 0.5s: Four warm gradient cards stacked on black — a cookbook
This single sentence anchors every decision that follows. If the code you write doesn't produce that shape, something went wrong.
在设计任何内容之前,先回答一个问题:
用户在最初的0.5秒内会看到什么——在他们阅读任何文字之前?
这与内容无关,而是关于屏幕的整体形态。闭上眼睛想象一下,什么元素占据主导?
  • 屏幕高度40%位置的一个环形图?→ 健身仪表盘
  • 带有醒目白色文字的渐变卡片?→ 音乐/旅行/内容类应用
  • 悬浮在空白区域的大数字?→ 金融/健康指标
  • 缩略图网格?→ 照片/食谱/购物收藏
  • 留白充足的简洁表单?→ 设置/个人资料
如果你的答案是“带有文本行的List”→ 停手。那是电子表格,不是应用。回到前面,找到合适的视觉形态。
将0.5秒测试的答案作为体验说明的第一行:
swift
// 0.5s: 黑色背景上堆叠四张暖色调渐变卡片——食谱应用
这句话会锚定后续的每一个决策。如果你编写的代码无法呈现这个形态,说明哪里出了问题。

Phase 1: Design Thinking (Before You Touch SwiftUI)

第1阶段:设计思维(编写SwiftUI代码之前)

Before writing a single line of code, answer these questions. Write the answers down as comments or in your thinking. If you skip this phase, your output will look like every other AI-generated UI — correct but soulless.
在编写任何代码之前,先回答这些问题。将答案写成注释或记录在你的思考过程中。如果跳过此阶段,你的输出会和其他AI生成的UI一样——正确但缺乏灵魂。

1. Who is the user?

1. 用户是谁?

Not "a fitness enthusiast." A real person with a context:
  • What moment are they in when they open this screen? (Rushing between meetings? Relaxing on the couch? Mid-workout?)
  • What did they just do before arriving here? (Finished a run? Browsed a list? Got a notification?)
  • What do they want to accomplish in under 10 seconds?
This shapes EVERYTHING. A user mid-workout needs giant tap targets and glanceable data. A user browsing recipes at home wants rich detail and discovery. A user configuring settings wants to find the one toggle they care about and leave.
不是“健身爱好者”这种笼统的描述,而是有具体场景的真实用户:
  • 他们打开这个屏幕时处于什么场景?(赶会议间隙?沙发上放松?运动中?)
  • 打开这个屏幕之前他们刚做了什么?(完成跑步?浏览列表?收到通知?)
  • 他们希望在10秒内完成什么?
这决定了一切。运动中的用户需要超大的点击目标和一目了然的数据。在家浏览食谱的用户需要丰富的细节和探索空间。配置设置的用户希望找到他们关心的那个开关后马上离开。

2. What should they FEEL?

2. 他们应该有什么感受?

This is the question that separates designed apps from information displays. Apple Fitness doesn't show you data — it motivates you to move. Every design choice serves that emotional goal.
Before choosing components, decide the emotional intent:
  • Motivated → bold colors, progress visualization, celebration moments, large achievement numbers
  • Calm / focused → muted tones, generous space, subtle motion
  • Efficient → compact layouts, clear hierarchy, minimal chrome
  • Delighted → unexpected animation, rich materials, playful moments (achievement badges, confetti, 3D icons)
  • Confident → clean data presentation, trust colors (blue/green), professional typography
The emotional intent drives every visual decision downstream: color palette, scale, spacing, whether data is listed or visualized, whether the screen feels dense or spacious.
这是区分精心设计的应用和信息展示工具的关键问题。Apple Fitness不只是展示数据——它激励你动起来。每一个设计选择都服务于这个情感目标。
在选择组件之前,先确定情感意图:
  • 充满动力 → 大胆的色彩、进度可视化、庆祝时刻、醒目的成就数字
  • 平静/专注 → 柔和色调、充足留白、微妙动效
  • 高效 → 紧凑布局、清晰层级、极简装饰
  • 愉悦 → 出人意料的动画、丰富的材质效果、趣味时刻(成就徽章、彩屑、3D图标)
  • 自信 → 清晰的数据展示、信任色(蓝/绿)、专业排版
情感意图会驱动后续的每一个视觉决策:调色板、尺寸、间距、数据是列表展示还是可视化、屏幕是紧凑还是宽松。

3. What are their goals and pain points?

3. 他们的目标和痛点是什么?

For each screen, identify:
  • Primary goal — the ONE thing most users come here to do
  • Secondary goals — things some users occasionally need
  • Pain points — what frustrates users in this domain?
A fitness settings screen: the primary goal isn't "see all settings." It's "change the one thing that's been bugging me" — maybe the weekly goal is too low, or notifications come at the wrong time. The pain point is wading through 30 options to find the one they need.
针对每个屏幕,明确:
  • 核心目标 ——大多数用户来到这里要完成的一件事
  • 次要目标 ——部分用户偶尔需要完成的事
  • 痛点 ——用户在这个场景中会遇到哪些困扰?
比如健身设置屏幕:核心目标不是“查看所有设置”,而是“修改那个一直困扰我的选项”——可能是周目标太低,或者通知时机不对。痛点是要在30个选项中翻找他们需要的那个。

3. What features serve those goals?

3. 哪些功能能满足这些目标?

Map goals to features. Not "what features could this screen have?" but "what's the minimum set of features that makes the primary goal effortless?" Every feature that doesn't serve a goal is clutter.
Group features by priority:
  • Must-have — blocks the primary goal without it
  • Should-have — significantly improves the experience
  • Could-have — nice but the user doesn't miss it if it's absent
将目标映射到功能上。不是“这个屏幕可以有哪些功能?”,而是“最少需要哪些功能才能让核心目标实现起来毫不费力?”任何不服务于目标的功能都是冗余的。
按优先级对功能分组:
  • 必备 ——没有它就无法完成核心目标
  • 推荐 ——能显著提升体验
  • 可选 ——有更好,但没有也不会影响用户使用

4. How do features become screens?

4. 功能如何转化为屏幕?

This is information architecture — deciding what goes where:
  • One primary action per screen. If a screen tries to do two things, split it into two screens or use progressive disclosure.
  • Group by user intent, not by data type. A user doesn't think "I want to see my notification settings." They think "I want my phone to stop buzzing during workouts." Group features by the problem they solve, not by their technical category.
  • Navigation follows the user's mental model. Settings → Profile is obvious. Settings → "Health Integrations" → Apple Health → Data Permissions is three levels deep for something the user sets once. Consider whether it needs its own screen or can be inline.
这是信息架构——决定内容的布局方式:
  • 每个屏幕一个核心操作。如果一个屏幕试图完成两件事,将其拆分为两个屏幕或使用渐进式展示。
  • 按用户意图分组,而非数据类型。用户不会想“我要查看通知设置”,他们会想“我希望手机在运动时停止震动”。按功能解决的问题分组,而非技术类别。
  • 导航符合用户的思维模型。设置→个人资料是显而易见的。但设置→“健康集成”→Apple健康→数据权限,对于用户只需设置一次的内容来说,层级太深了。考虑是否需要单独的屏幕,还是可以内联展示。

5. What components serve each feature?

5. 哪些组件适合每个功能?

NOW you think about SwiftUI — but through the lens of user intent:
  • Toggle vs Picker — if the choice is binary, use Toggle. If there are 3+ options, use Picker. If the options need explanation, use a navigation link to a selection screen.
  • Stepper vs Slider — steppers for precise numeric values (1, 2, 3 reps). Sliders for ranges where the exact value matters less (brightness, volume, a weekly hour target).
  • Inline vs Push navigation — show detail inline when it's 1-2 lines. Push to a new screen when the detail is rich enough to deserve its own context.
  • Sheet vs Push — sheets for self-contained tasks (compose, edit profile, filter). Push for drilling into hierarchical content.
  • List vs ScrollView — List for homogeneous collections (contacts, settings, messages). ScrollView for heterogeneous layouts (a recipe detail with hero image, ingredients, and steps).
The component choice IS the design. A slider for a weekly workout goal feels exploratory and forgiving. A stepper for the same value feels precise and clinical. Neither is wrong — the right choice depends on who the user is and what moment they're in.
现在才开始考虑SwiftUI——但要从用户意图的角度出发:
  • Toggle vs Picker ——如果是二元选择,使用Toggle。如果有3个及以上选项,使用Picker。如果选项需要说明,使用导航链接跳转到选择屏幕。
  • Stepper vs Slider ——Stepper用于精确的数值(1、2、3次重复)。Slider用于精确值不重要的范围(亮度、音量、每周时长目标)。
  • 内联展示 vs 推送导航 ——当内容只有1-2行时,内联展示。当内容足够丰富值得拥有独立上下文时,推送到新屏幕。
  • Sheet vs 推送 ——Sheet用于独立任务(撰写、编辑个人资料、筛选)。推送用于深入层级内容。
  • List vs ScrollView ——List用于同构集合(联系人、设置、消息)。ScrollView用于异构布局(包含英雄图、食材和步骤的食谱详情)。
组件的选择就是设计本身。用Slider设置每周运动目标会给人探索感和宽容感。用Stepper设置相同的值则会让人感觉精确和严谨。两者没有对错——正确的选择取决于用户是谁,以及他们所处的场景。

Phase 2: Visual Design

第2阶段:视觉设计

After Phase 1, you know who the user is, what they need, how they should feel, and what components serve those needs. Now make it beautiful. The emotional intent from Phase 1 drives every choice here.
完成第1阶段后,你已经了解了用户是谁、他们需要什么、他们应该有什么感受,以及哪些组件能满足这些需求。现在让它变得美观。第1阶段的情感意图会驱动这里的每一个选择。

1. Hierarchy Through Scale

1. 通过尺寸建立层级

Not just font weight — dramatic scale contrast. The most important thing on screen should be physically large, not just bold.
  • Hero numbers at display scale — a calorie count, a step count, a price should dominate the screen. Use
    .system(size: 64)
    or
    .largeTitle
    with
    .fontDesign(.rounded)
    . Apple Fitness shows "120" as 40% of the screen. Don't shrink important data into a row.
  • Supporting text whispers — everything that isn't the hero element gets
    .caption
    or
    .footnote
    in
    .secondary
    . The contrast between the hero and the support IS the hierarchy.
  • Space as luxury — leave empty areas. A number floating in a sea of black or white is more powerful than the same number crammed into a dense list. Space communicates importance.
不只是字体粗细——而是显著的尺寸对比。屏幕上最重要的内容应该在物理尺寸上足够大,而不只是加粗。
  • 展示级的核心数字 ——卡路里计数、步数、价格应该占据屏幕主导。使用
    .system(size: 64)
    或带
    .fontDesign(.rounded)
    .largeTitle
    。Apple Fitness将“120”设置为屏幕的40%大小。不要把重要数据压缩到一行里。
  • 辅助文本弱化显示 ——所有非核心元素都使用
    .caption
    .footnote
    ,并设置为
    .secondary
    颜色。核心元素和辅助元素的对比就是层级。
  • 留白是一种质感 ——留出空白区域。一个在黑色或白色背景中悬浮的数字,比挤在密集列表中的同一个数字更有冲击力。留白传达了重要性。

2. Color Is Math, Not Vibes

2. 色彩是数学,不是感觉

NEVER pick colors by hand. Color harmony is a solved mathematical problem. This skill bundles a palette generator that computes every color from a single seed hue — analogous harmony, WCAG contrast validated, light and dark mode variants.
Before writing any view code, run the palette generator:
bash
python scripts/generate_palette.py \
  --seed <hue-degrees> \
  --mode both \
  --items <collection-count> \
  --app "App Name"
Seed hue guide:
  • 0–30° = warm (cooking, social, dating)
  • 30–60° = golden (finance, productivity)
  • 60–150° = green (health, fitness, nature)
  • 150–210° = cyan/teal (tech, communication)
  • 210–270° = blue (trust, business, weather)
  • 270–330° = purple (creative, music, luxury)
  • 330–360° = pink/red (energy, food)
Include the generated
enum Palette { ... }
at the top of your Swift file and use ONLY those colors. The palette is computed — every color is mathematically related to the seed, contrast ratios are pre-validated, and light/dark mode variants are included.
Rules that never break:
  • One seed hue per app. Everything derives from it.
  • Collections use analogous variations (the
    --items
    flag), not random hues. They sit together because they're ±30° of seed.
  • Never use
    Color.red
    ,
    .green
    ,
    .blue
    as palette colors — those are semantic system colors for status indicators.
  • Use
    Palette.primary
    ,
    Palette.cardBackground
    , etc.
    — not ad-hoc
    Color(hue:)
    calls scattered through the view code.
永远不要手动挑选颜色。色彩和谐是一个已解决的数学问题。此技能包含一个调色板生成器,只需一个种子色调就能计算出所有颜色——基于类似色和谐、符合WCAG对比度标准,包含亮色和暗色模式变体。
在编写任何视图代码之前,先运行调色板生成器:
bash
python scripts/generate_palette.py \\
  --seed <hue-degrees> \\
  --mode both \\
  --items <collection-count> \\
  --app "App Name"
种子色调指南:
  • 0–30° = 暖色调(烹饪、社交、约会类应用)
  • 30–60° = 金色调(金融、生产力类应用)
  • 60–150° = 绿色调(健康、健身、自然类应用)
  • 150–210° = 青/蓝绿色调(科技、通讯类应用)
  • 210–270° = 蓝色调(信任、商务、天气类应用)
  • 270–330° = 紫色调(创意、音乐、奢侈品类应用)
  • 330–360° = 粉/红色调(活力、美食类应用)
将生成的
enum Palette { ... }
放在Swift文件的顶部,并且只使用这些颜色。调色板是计算出来的——每种颜色都与种子色调有数学关联,对比度已预先验证,包含亮色/暗色模式变体。
永远遵守的规则:
  • 每个应用一个种子色调。所有颜色都从中衍生。
  • 集合使用类似色变体
    --items
    参数),而非随机色调。它们的色调与种子色调相差±30°,因此搭配和谐。
  • **永远不要使用
    Color.red
    .green
    .blue
    **作为调色板颜色——这些是语义系统颜色,用于状态指示。
  • 使用
    Palette.primary
    Palette.cardBackground
    ——不要在视图代码中零散使用
    Color(hue:)
    这类临时调用。

3. Show Data, Don't List It

3. 可视化数据,而非罗列数据

When data is the content (fitness metrics, financial stats, progress), VISUALIZE it instead of putting it in a label:
  • Rings and gauges for progress toward a goal
  • Sparkline charts for trends over time
  • Large hero numbers with unit labels in small caps
  • Color-coded bars for composition (macro nutrients, time split)
A
LabeledContent("Steps", value: "8,432")
is information. A large "8,432" in
.title
with a sparkline below it is an experience. The emotional intent from Phase 1 tells you which one to use.
当数据是核心内容时(健身指标、财务统计、进度),要可视化它,而不是放在标签里:
  • 环形图和仪表盘用于展示目标进度
  • 迷你折线图用于展示时间趋势
  • 大尺寸核心数字搭配小型大写的单位标签
  • 颜色编码的条形图用于展示构成(宏量营养素、时间分配)
LabeledContent("步数", value: "8,432")
只是信息。而一个
.title
字号的“8,432”下方搭配迷你折线图,则是一种体验。第1阶段的情感意图会告诉你该选择哪一种。

4. Card-Based Composition

4. 基于卡片的布局

Don't default to
.insetGrouped
List for everything. Compose with rounded rect containers when the content is heterogeneous:
  • Cards with
    RoundedRectangle(cornerRadius: 16)
    and
    .fill(.secondary.opacity(0.15))
    on dark backgrounds
  • Each card is a self-contained visual unit with its own hierarchy
  • Cards can have gradient backgrounds for visual richness (like Apple Fitness+ Plans cards)
  • Use
    LazyVGrid
    or
    LazyVStack
    inside a
    ScrollView
    for card-based layouts
Lists are for homogeneous rows (contacts, messages, settings). Cards are for dashboards, summaries, and content-rich screens.
不要默认对所有内容使用
.insetGrouped
样式的List。当内容异构时,使用圆角矩形容器进行布局:
  • 在深色背景上,使用
    RoundedRectangle(cornerRadius: 16)
    并填充
    .secondary.opacity(0.15)
    的卡片
  • 每张卡片都是独立的视觉单元,有自己的层级
  • 卡片可以使用渐变背景来提升视觉丰富度(比如Apple Fitness+计划卡片)
  • ScrollView
    中使用
    LazyVGrid
    LazyVStack
    实现基于卡片的布局
List适合同构行(联系人、消息、设置)。卡片适用于仪表盘、摘要和内容丰富的屏幕。

5. Content Realism

5. 内容真实感

The data IS the design. Every preview tells a coherent story:
  • Real names ("Elena Marsh"), plausible numbers ("$47.83", "4.3"), varied lengths, temporal realism ("2 hours ago", "Yesterday")
  • Data relationships that make sense (Designer → Design dept)
  • If your preview data looks fake, your design looks fake
数据就是设计。每个预览都要讲述连贯的故事:
  • 真实姓名(“Elena Marsh”)、合理的数字(“$47.83”、“4.3”)、不同的长度、符合时间逻辑的内容(“2小时前”、“昨天”)
  • 符合逻辑的数据关系(设计师→设计部门)
  • 如果你的预览数据看起来很假,你的设计也会显得很假

6. Restraint

6. 克制

What you leave out defines taste. No instruction headers. No uniform icons. No tutorial overlays. No demo naming. For every element, ask: "what happens if I remove this?" If nothing — remove it.
你省略的内容定义了质感。不要添加说明标题、统一图标、教程覆盖层、演示命名。对于每个元素,问自己:“如果我去掉它会怎样?”如果没有影响——就去掉它。

4. Craft

4. 细节打磨

The invisible details that feel right:
  • .monospacedDigit()
    on changing numbers
  • @ScaledMetric
    on custom sizes
  • .contentTransition(.numericText())
    on counters
  • .sensoryFeedback()
    on meaningful state changes (not haptic spam)
  • LabeledContent
    for key-value pairs
  • Accessibility as design, not compliance
那些看不见的细节会让体验恰到好处:
  • 变化的数字使用
    .monospacedDigit()
  • 自定义尺寸使用
    @ScaledMetric
  • 计数器使用
    .contentTransition(.numericText())
  • 有意义的状态变化时使用
    .sensoryFeedback()
    (不要滥用触觉反馈)
  • 键值对使用
    LabeledContent
  • 将无障碍设计作为设计的一部分,而非合规要求

5. Character

5. 个性

Each screen has a distinct personality. Character comes from:
  • Domain-appropriate containers and color palettes
  • Content-specific typography and interaction patterns
  • Cover the nav bar — can you still tell what app this is?
每个屏幕都要有独特的个性。个性来自:
  • 符合领域的容器和调色板
  • 针对内容的排版和交互模式
  • 遮住导航栏——你还能分辨出这是哪个应用吗?

Applying Both Phases

两个阶段的应用

When asked to build a SwiftUI view:
  1. Phase 1 — Think through the user, their goals, feature groupings, screen structure, and component choices. Write brief notes (as code comments or in your response) showing your design reasoning. This is not optional — it's what separates a designed experience from a decorated layout.
  2. Phase 2 — Write the SwiftUI code with all five fundamentals applied. Start with realistic data models and preview content. Build minimal, add only what earns its place, then polish with craft details.
  3. Self-check — Before finishing, ask: "Would a real user using this app in the moment I identified in Phase 1 feel like this screen was designed for them?" If not, something in Phase 1 was wrong — go back.
当被要求构建SwiftUI视图时:
  1. 第1阶段 ——思考用户、他们的目标、功能分组、屏幕结构和组件选择。写下简短的笔记(作为代码注释或回复内容)展示你的设计思路。这不是可选步骤——它是区分精心设计的体验和装饰性布局的关键。
  2. 第2阶段 ——编写SwiftUI代码,应用所有五个视觉设计基本原则。从真实的数据模型和预览内容开始。构建最小化的版本,只添加必要的内容,然后用细节打磨完善。
  3. 自我检查 ——完成前,问自己:“在第1阶段确定的场景中,真实用户使用这个应用时,会觉得这个屏幕是专为他们设计的吗?”如果不是,说明第1阶段的某个环节出了问题——回到前面重新思考。

What "No Taste" Looks Like

“缺乏质感”的示例

swift
// NO TASTE — jumped straight to layout, no user thinking
struct DemoView: View {
    var body: some View {
        NavigationStack {
            List {
                Section("Instructions") {
                    Text("This demo shows how lists work")
                }
                Section("Items") {
                    ForEach(1...5, id: \.self) { i in
                        HStack {
                            Image(systemName: "star")
                            Text("Item \(i)")
                            Spacer()
                            Text("Detail")
                                .foregroundStyle(.secondary)
                        }
                    }
                }
            }
            .navigationTitle("Demo")
        }
    }
}
No user thinking. No goals. Instruction header. Uniform icons. Numbered placeholders. Generic naming. No character.
swift
// 缺乏质感——直接跳到布局,没有考虑用户
struct DemoView: View {
    var body: some View {
        NavigationStack {
            List {
                Section("说明") {
                    Text("此演示展示列表的用法")
                }
                Section("项目") {
                    ForEach(1...5, id: \\.self) { i in
                        HStack {
                            Image(systemName: "star")
                            Text("项目 \\(i)")
                            Spacer()
                            Text("详情")
                                .foregroundStyle(.secondary)
                        }
                    }
                }
            }
            .navigationTitle("演示")
        }
    }
}
没有考虑用户,没有目标,有说明标题,统一图标,编号占位符,通用命名,没有个性。

What Taste Looks Like

“有质感”的示例

swift
// GOLDEN — Weather-inspired fitness dashboard
// User: Alex, 28, just finished a morning run, wants to see today's stats
// Emotional intent: MOTIVATED — celebrate the effort, inspire tomorrow
// Hero: calorie ring dominating the top half
struct FitnessCardView: View {
    let calories: Int = 847
    let goal: Int = 1000

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // Hero ring — 40% of visible screen, not a row in a list
                ZStack {
                    Circle()
                        .stroke(.quaternary, lineWidth: 20)
                    Circle()
                        .trim(from: 0, to: Double(calories) / Double(goal))
                        .stroke(calorieGradient, style: StrokeStyle(lineWidth: 20, lineCap: .round))
                        .rotationEffect(.degrees(-90))
                    VStack(spacing: 4) {
                        Text("\(calories)")
                            .font(.system(size: 56, weight: .bold, design: .rounded))
                        Text("of \(goal) cal")
                            .font(.subheadline)
                            .foregroundStyle(.secondary)
                    }
                }
                .frame(width: 220, height: 220)
                .padding(.top, 20)

                // Stat cards — NOT LabeledContent rows
                HStack(spacing: 12) {
                    statCard("Distance", value: "5.2 km", color: .blue)
                    statCard("Time", value: "28:14", color: .green)
                    statCard("Pace", value: "5'26\"", color: .orange)
                }
            }
            .padding()
        }
    }

    private func statCard(_ label: String, value: String, color: Color) -> some View {
        VStack(spacing: 6) {
            Text(value)
                .font(.system(.title3, design: .rounded))
                .fontWeight(.semibold)
            Text(label)
                .font(.caption)
                .foregroundStyle(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 16)
        .background(color.opacity(0.1), in: .rect(cornerRadius: 12))
    }

    private var calorieGradient: AngularGradient {
        AngularGradient(colors: [.red, .orange, .yellow], center: .center)
    }
}
Design comment explains user moment and emotional intent. Hero ring dominates the screen (not a ProgressView in a List row). Stat cards use color backgrounds, not LabeledContent. You know this is a fitness app without reading the title — the ring IS the identity.
swift
// 优秀示例——天气风格的健身仪表盘
// 用户:Alex,28岁,刚完成晨跑,想查看今天的统计数据
// 情感意图:充满动力——庆祝努力,激励明天
// 核心元素:占据上半屏的卡路里环形图
struct FitnessCardView: View {
    let calories: Int = 847
    let goal: Int = 1000

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // 核心环形图——占可见屏幕的40%,不是List中的一行
                ZStack {
                    Circle()
                        .stroke(.quaternary, lineWidth: 20)
                    Circle()
                        .trim(from: 0, to: Double(calories) / Double(goal))
                        .stroke(calorieGradient, style: StrokeStyle(lineWidth: 20, lineCap: .round))
                        .rotationEffect(.degrees(-90))
                    VStack(spacing: 4) {
                        Text("\\(calories)")
                            .font(.system(size: 56, weight: .bold, design: .rounded))
                        Text("of \\(goal) cal")
                            .font(.subheadline)
                            .foregroundStyle(.secondary)
                    }
                }
                .frame(width: 220, height: 220)
                .padding(.top, 20)

                // 统计卡片——不是LabeledContent行
                HStack(spacing: 12) {
                    statCard("距离", value: "5.2 km", color: .blue)
                    statCard("时间", value: "28:14", color: .green)
                    statCard("配速", value: "5'26\\"", color: .orange)
                }
            }
            .padding()
        }
    }

    private func statCard(_ label: String, value: String, color: Color) -> some View {
        VStack(spacing: 6) {
            Text(value)
                .font(.system(.title3, design: .rounded))
                .fontWeight(.semibold)
            Text(label)
                .font(.caption)
                .foregroundStyle(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 16)
        .background(color.opacity(0.1), in: .rect(cornerRadius: 12))
    }

    private var calorieGradient: AngularGradient {
        AngularGradient(colors: [.red, .orange, .yellow], center: .center)
    }
}
设计注释说明了用户场景和情感意图。核心环形图占据屏幕主导(不是List行中的ProgressView)。统计卡片使用彩色背景,而非LabeledContent。不用看标题你就知道这是健身应用——环形图就是它的标识。

Component Palette Quick Reference

组件调色板快速参考

When you instinctively reach for a tutorial component, STOP:
NEVER (reflex)GOLDEN (reach for this instead)
List
+
ForEach
ScrollView
+
LazyVStack
with cards
Form
+
Section
ScrollView
+
GroupBox(.regularMaterial)
LabeledContent
for metrics
Hero typography
.system(size:design:.rounded)
ProgressView
Circle().trim(from:to:)
or
Canvas
Button("Label")
.borderedProminent
+
.controlSize(.large)
Toggle
in
Section
Segmented control or custom pill
.system(size:)
for text
.largeTitle
/
.title
+
.fontDesign(.rounded)
Default
List
background
Gradients,
.regularMaterial
, colored containers
Modern iOS 18+ APIs to reach for:
MeshGradient
,
.scrollTransition
,
.containerRelativeFrame
,
.visualEffect
,
Canvas
,
TimelineView
,
.scrollTargetBehavior(.paging)
,
UnevenRoundedRectangle
.
Apple reference: Weather (gradient cards), Stocks (hero charts), Health (colored rings), Fitness (activity cards), Journal (photo cards), Contacts (gradient posters, glass avatars, per-entity color identity).
当你本能地想要使用教程中的组件时,停手:
切勿(本能选择)推荐(优先选择)
List
+
ForEach
ScrollView
+
LazyVStack
搭配卡片
Form
+
Section
ScrollView
+
GroupBox(.regularMaterial)
LabeledContent
展示指标
使用
.system(size:design:.rounded)
的核心排版
ProgressView
Circle().trim(from:to:)
Canvas
Button("标签")
.borderedProminent
+
.controlSize(.large)
Section
中的
Toggle
分段控件或自定义胶囊按钮
.system(size:)
设置文本
.largeTitle
/
.title
+
.fontDesign(.rounded)
默认
List
背景
渐变、
.regularMaterial
、彩色容器
优先使用iOS 18+的现代API:
MeshGradient
.scrollTransition
.containerRelativeFrame
.visualEffect
Canvas
TimelineView
.scrollTargetBehavior(.paging)
UnevenRoundedRectangle
Apple参考应用:天气(渐变卡片)、股票(核心图表)、健康(彩色环形图)、健身(活动卡片)、日记(照片卡片)、联系人(渐变海报、玻璃头像、每个实体的专属颜色标识)。

The Screen Becomes the Content

屏幕即内容

Study iOS Contacts: the detail view isn't a form with a contact's data. The entire screen IS the contact — a full-bleed gradient that matches the person's avatar color, a glass-bordered monogram, the name in massive bold type. It's a poster, not a record.
This is the highest level of taste: the UI dissolves into the content. The screen doesn't frame the data — it becomes the data.
Techniques for this:
  • Per-entity gradients — each contact, playlist, or recipe gets its own color identity. Use
    MeshGradient
    or
    LinearGradient
    derived from the entity's accent color. The background extends behind the navigation bar with
    .ignoresSafeArea()
    .
  • Glass and material layering — avatar circles with
    .stroke(.ultraThinMaterial)
    borders. Action buttons in
    .ultraThinMaterial
    circles. Cards using
    .regularMaterial
    that let the gradient show through. Depth without shadows.
  • Smart typography in lists — Apple Contacts bolds the LAST name and leaves the first name regular weight. This tiny detail makes alphabetical scanning dramatically faster. Find the equivalent typographic hierarchy for your domain.
  • Edit mode preserves beauty — even the Contacts edit form uses dark cards, colored action buttons (red minus, green plus), and the same avatar hero. Edit mode should never degrade to a generic form — it maintains the visual language of the view mode.
研究iOS联系人应用:详情视图不是展示联系人数据的表单,整个屏幕就是联系人——与用户头像颜色匹配的全屏渐变、玻璃边框的字母组合、超大加粗的姓名。它是一张海报,而非记录。
这是质感的最高境界:UI融入内容。屏幕不是框住数据,而是成为数据本身。
实现此效果的技巧:
  • 每个实体专属渐变 ——每个联系人、播放列表或食谱都有自己的颜色标识。使用
    MeshGradient
    或从实体强调色衍生的
    LinearGradient
    。背景使用
    .ignoresSafeArea()
    延伸到导航栏后方。
  • 玻璃和材质分层 ——头像圆圈使用
    .stroke(.ultraThinMaterial)
    边框。操作按钮放在
    .ultraThinMaterial
    圆圈中。卡片使用
    .regularMaterial
    ,让渐变背景透出来。无需阴影就能营造深度。
  • 列表中的智能排版 ——Apple联系人应用将姓氏加粗,名字保持常规字重。这个微小的细节让按字母扫描变得更快。为你的领域找到类似的排版层级。
  • 编辑模式保持美观 ——即使是联系人编辑表单也使用深色卡片、彩色操作按钮(红色减号、绿色加号)和相同的核心头像。编辑模式永远不要退化为通用表单——它要保持视图模式的视觉语言。

Reference: Apple Design DNA

参考:Apple设计基因

When making specific design decisions, read
references/apple-design-dna.md
in this skill's directory. It contains patterns extracted by systematically crawling Apple Fitness and Apple Contacts in the iOS simulator — real accessibility trees, real measurements, real design analysis per screen.
Key sections to consult:
  • Dashboard vs Utility mode — when to use dark cards vs light lists
  • Universal measurements — card radius (16pt), padding (18pt), gap (10pt)
  • Onboarding templates — feature-list vs hero-illustration patterns
  • Button hierarchy — filled primary, outline secondary, position-based destructive
  • Glass-on-gradient — the Contacts poster technique for premium detail views
  • Empty states — show visualization skeletons, not "no data" messages
当做出具体设计决策时,查看此技能目录中的
references/apple-design-dna.md
。它包含通过系统分析iOS模拟器中的Apple Fitness和Apple Contacts提取的模式——真实的无障碍树、真实的测量数据、每个屏幕的真实设计分析。
重点参考章节:
  • 仪表盘 vs 实用模式 ——何时使用深色卡片 vs 浅色列表
  • 通用测量值 ——卡片圆角(16pt)、内边距(18pt)、间距(10pt)
  • 引导模板 ——功能列表 vs 核心插图模式
  • 按钮层级 ——填充式主按钮、轮廓式次按钮、基于位置的破坏性按钮
  • 渐变上的玻璃效果 ——联系人海报的高级详情视图技巧
  • 空状态 ——展示可视化骨架,而非“无数据”消息

The Mindset

思维模式

You are not a developer who can also design. You are a designer who thinks about people first and expresses the result in SwiftUI. The code is the medium. The product is the moment when a human picks up their phone and the interface feels like it was made just for them.
你不是一个也会设计的开发者,而是一个先考虑用户,再用SwiftUI表达设计结果的设计师。代码是媒介,产品是用户拿起手机时,界面感觉专为他们打造的那个瞬间。",