mcpField
I
ios-taste

ios-taste

github.com/pproenca设计与 UI218267 次安装11 周前更新
代码仓库

关于 ios-taste

ios-taste 是一项 SwiftUI 界面设计技能,强调先理解用户需求再编写代码,并结合产品思考、视觉构图和调色板生成脚本指导设计。

主要功能

  1. 01分析用户情境与目标
  2. 02进行 0.5 秒屏幕形态测试
  3. 03提供 SwiftUI 组件选择建议
  4. 04生成并检查对比度的调色板
  5. 05指导视觉层级与数据呈现

适用场景

  1. 01围绕用户当下目标规划 SwiftUI 界面
  2. 02为设置页、仪表盘或内容页选择组件
  3. 03创建协调的浅色与深色调色板

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pproenca/dot-skills --skill ios-taste

安装后在 Claude Code 中输入/ios-taste确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
metadata.json
references/
apple-design-dna.md
scripts/
generate_palette.py
verify_palette.py

详细介绍

iOS 品味

品味并非始于像素层面,而是始于“这个人是谁,他们需要什么?”视觉打磨是最后一步。第一步是深入了解用户的世界,让界面设计显得顺理成章——仿佛它本就只能如此设计。

你的默认模式会直接跳到布局。它生成的 SwiftUI 在技术上正确,却显得千篇一律,因为它从未立足于真实用户的需求。这项技能会改变操作顺序:先像设计师一样思考,再编写代码。

阶段 0:0.5 秒测试(先明确方向,再做其他事)

设计任何内容之前,先回答一个问题:

用户在最初的半秒里会看到什么——在他们读到任何文字之前?

这不是关于内容,而是关于屏幕的形状。闭上眼睛想象一下。什么最醒目?

  • 屏幕高度 40% 处的一个圆环?→ 健身仪表盘
  • 带有醒目白色文字的渐变卡片?→ 音乐/旅行/内容
  • 悬浮在空间中的一个大数字?→ 财务/健康指标
  • 一个缩略图网格?→ 照片/食谱/购物合集
  • 一个留白充足的简洁表单?→ 设置/个人资料

如果你的答案是“带有文本行的列表” → 停下。这是电子表格,不是应用。回去重新寻找视觉形状。

将 0.5 秒答案写为体验概要的第一行:

// 0.5s: Four warm gradient cards stacked on black — a cookbook

这一个句子为之后的每项决策奠定基础。如果你写出的代码没有呈现这种形状,那就说明哪里出了问题。

阶段 1:设计思考(动手编写 SwiftUI 之前)

在编写第一行代码之前,先回答这些问题。将答案写成注释,或记在思考过程中。如果跳过这一阶段,你的输出就会和其他 AI 生成的 UI 一样——正确,却毫无灵魂。

1. 用户是谁?

不是“健身爱好者”,而是一个处于特定情境中的真实用户:

  • 他们打开这个屏幕时,正处于什么时刻?(在会议间赶路?在沙发上放松?正在锻炼?)
  • 他们来这里之前刚做了什么?(刚跑完步?刚浏览过列表?刚收到通知?)
  • 他们希望在不到 10 秒内完成什么?

这会影响一切。正在锻炼的用户需要大尺寸的点击区域和一目了然的数据。在家浏览食谱的用户想要丰富的细节和探索体验。配置设置的用户只想找到自己在意的那个开关,然后离开。

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

这个问题区分了经过设计的应用和信息展示界面。Apple Fitness 不只是向你展示数据——它会激励你运动。每项设计选择都服务于这一情感目标。

选择组件之前,先确定情感意图:

  • 充满动力 → 醒目的颜色、进度可视化、庆祝时刻、大号成就数字
  • 平静/专注 → 柔和的色调、充足的留白、细腻的动效
  • 高效 → 紧凑的布局、清晰的层级、尽量减少界面装饰
  • 惊喜愉悦 → 出乎意料的动画、丰富的材质、有趣的时刻(成就徽章、彩纸、3D 图标)
  • 充满信心 → 清晰的数据呈现、值得信赖的颜色(蓝色/绿色)、专业的字体排印

情感意图会决定之后的每项视觉决策:配色方案、大小、间距、数据是以列表还是可视化形式呈现,以及屏幕给人的感觉是紧凑还是宽敞。

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

针对每个屏幕,明确:

  • 主要目标 — 大多数用户来这里要做的那一件事
  • 次要目标 — 部分用户偶尔需要做的事
  • 痛点 — 这个领域里什么最让用户烦恼?

健身设置屏幕的主要目标不是“查看所有设置”,而是“修改一直让我烦恼的那一项”——也许是每周目标太低,或者通知总是在不合适的时间出现。痛点在于要翻遍 30 个选项,才能找到自己需要的那一个。

3. 哪些功能可以实现这些目标?

将目标映射到功能。不要问“这个界面可以有哪些功能?” 而要问“要让核心目标轻松达成,最少需要哪些功能?” 任何不服务于目标的功能都是杂乱。

按优先级对功能分组:

  • 必备——缺少它就无法达成核心目标
  • 应该具备——能显著改善体验
  • 可以具备——锦上添花,但缺少它用户也不会在意
4. 如何将功能转化为屏幕?

这是信息架构——决定内容放在哪里:

  • 每个屏幕只设一个主要操作。 如果一个屏幕试图完成两件事,就拆分成两个屏幕,或者采用渐进式披露。
  • 按用户意图分组,而不是按数据类型分组。 用户不会想“我想查看通知设置”,而是会想“我想让手机在锻炼时别再嗡嗡响”。按功能要解决的问题分组,而不是按其技术类别分组。
  • 导航应符合用户的心智模型。“设置 → 个人资料”一目了然。“设置 → 健康集成 → Apple Health → 数据权限”却要深入三层,才能找到用户只设置一次的选项。考虑它是否需要单独的屏幕,还是可以内嵌显示。
5. 哪些组件适合各项功能?

现在才考虑 SwiftUI——但要从用户意图出发:

  • Toggle 还是 Picker——如果选项只有两种,就用 Toggle。如果有 3 种或更多选项,就用 Picker。如果选项需要说明,就使用导航链接,跳转到选择屏幕。
  • Stepper 还是 Slider——精确数值(1、2、3 次重复)用步进器。对于不太在意精确值的范围(亮度、音量、每周目标时长),用滑块。
  • 内嵌显示 还是 推入式导航——细节只有 1-2 行时,就内嵌显示。细节足够丰富、值得拥有独立上下文时,就推入新屏幕。
  • Sheet 还是 Push——Sheet 适用于独立任务(撰写、编辑个人资料、筛选)。Push 适用于深入浏览层级内容。
  • List 还是 ScrollView——List 适用于同质化内容集合(联系人、设置、消息)。ScrollView 适用于异质化布局(包含主图、食材和步骤的食谱详情)。

组件选择本身就是设计。用于每周锻炼目标的滑块让人感觉可以探索、灵活调整;同一数值用步进器则显得精准、严谨。两者都没有错——正确的选择取决于用户是谁,以及他们当下所处的情境。

阶段 2:视觉设计

完成阶段 1 后,你就知道用户是谁、他们需要什么、应该有什么感受,以及哪些组件能够满足这些需求。现在,让设计变得美观。阶段 1 确定的情感意图将指导这里的每项选择。

1. 通过尺寸建立层级

不只是调整字重——还要形成鲜明的尺寸对比。屏幕上最重要的内容应该在视觉上足够大,而不只是加粗。

  • 主视觉数字采用展示级尺寸——卡路里数、步数、价格,都应该占据屏幕的主导位置。使用 .system(size: 64) 或搭配 .fontDesign(.rounded) 的 .largeTitle。Apple Fitness 将“120”显示为屏幕面积的 40%。不要把重要数据缩进一行里。
  • 辅助文字轻声呈现——主视觉元素之外的内容,都使用 .secondary 的 .caption 或 .footnote。主视觉与辅助内容之间的对比本身就是层级。
  • 留白彰显格调——保留空白区域。一个数字漂浮在大片黑色或白色空间中,比挤在密集列表里的同一个数字更有力量。留白能传达重要性。
2. 色彩是数学,不是感觉

绝不要手动挑选颜色。色彩协调是一个已经解决的数学问题。此技能内置了调色板生成器,只需一个种子色相,就能计算出所有颜色——包含类似色协调、通过 WCAG 对比度验证,以及浅色和深色模式变体。

编写任何视图代码之前,先运行调色板生成器:

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. 展示数据,而不是罗列数据

当数据就是内容时(健身指标、财务统计、进度),应将其可视化,而不是放进标签里:

  • 进度环和仪表盘,用于展示目标完成情况
  • 迷你折线图,用于展示一段时间内的趋势
  • 醒目的大数字,搭配小型大写字母单位标签
  • 颜色编码条形图,用于展示组成情况(宏量营养素、时间分配)

LabeledContent("Steps", value: "8,432") 传达的是信息。在 .title 中显示醒目的“8,432”,下方再配一张迷你折线图,呈现的则是一种体验。第一阶段的情感意图会告诉你该用哪一种。

4. 卡片式布局

不要什么内容都默认使用 .insetGrouped List。内容类型不一时,应使用圆角矩形容器进行布局:

  • 在深色背景上使用带有 RoundedRectangle(cornerRadius: 16) 和 .fill(.secondary.opacity(0.15)) 的卡片
  • 每张卡片都是一个独立的视觉单元,拥有自己的层次结构
  • 卡片可以使用渐变背景,增添视觉丰富度(如 Apple Fitness+ Plans 卡片)
  • 在 ScrollView 中使用 LazyVGrid 或 LazyVStack 来构建 卡片式布局

列表适用于同质行(联系人、消息、设置)。 卡片适用于仪表盘、摘要和内容丰富的屏幕。

5. 内容真实感

数据就是设计。每个预览都应讲述一个连贯的故事:

  • 使用真实姓名(“Elena Marsh”)、合理数字(“$47.83”、“4.3”), 并让长度有所变化、时间信息贴近现实(“2 hours ago”、“Yesterday”)
  • 数据关系要合理(设计师 → 设计部门)
  • 如果预览数据看起来很假,设计也会显得很假
6. 克制

取舍体现品味。不要添加说明性标题、统一的图标、教程浮层或演示内容命名。对每个元素都问一句:“删掉它会怎样?”如果没有影响——就删掉。

4. 精雕细琢

那些不易察觉、却恰到好处的细节:

  • 对变化的数字使用 .monospacedDigit()
  • 对自定义尺寸使用 @ScaledMetric
  • 对计数器使用 .contentTransition(.numericText())
  • 对有意义的状态变化使用 .sensoryFeedback()(不要滥用触觉反馈)
  • 对键值对使用 LabeledContent
  • 将无障碍设计视为设计本身,而非合规要求
5. 个性

每个屏幕都应有鲜明的个性。个性来自:

  • 符合领域特点的容器和调色板
  • 适合内容的排版和交互模式
  • 遮住导航栏——你还能看出这是哪种应用吗?

应用两个阶段

当被要求构建 SwiftUI 视图时:

  1. 阶段 1 — 思考用户及其目标、功能分组、屏幕结构和组件选择。写下简短的设计说明(作为代码注释或在回复中),展示你的设计思路。这不是可选项——它正是精心设计的体验与装饰性布局之间的区别。

  2. 阶段 2 — 编写 SwiftUI 代码,并应用全部五项基本原则。从真实的数据模型和预览内容开始。先构建最精简的版本,只添加有意义的内容,然后通过精细打磨完善细节。

  3. 自检 — 完成前,先问自己:“在阶段 1 确定的使用场景中,真实用户使用这个 App 时,会觉得这个屏幕是为他们设计的吗?”如果不会,那阶段 1 的某个环节就有问题——回去重新思考。

“缺乏品味”是什么样子

// 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")
        }
    }
}

没有考虑用户。没有目标。指令式标题。千篇一律的图标。带编号的占位内容。泛泛的命名。毫无个性。

“有品味”是什么样子

// 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)
    }
}

设计注释说明了用户使用场景和情感意图。主视觉圆环占据屏幕的主导地位(而不是 List 行中的 ProgressView)。统计卡片使用彩色背景,而不是 LabeledContent。不用看标题,你也知道这是一个健身 App——圆环本身就是它的标识。

组件选择速查表

当你下意识地想用教程里的组件时,停下来:

不要用(条件反射)优先选择(改用这些)
List + ForEach带卡片的 ScrollView + LazyVStack
Form + SectionScrollView + GroupBox(.regularMaterial)
用 LabeledContent 展示指标英雄式排版 .system(size:design:.rounded)
ProgressViewCircle().trim(from:to:) 或 Canvas
Button("Label").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 参考:Weather(渐变卡片)、Stocks(主视觉图表)、Health(彩色圆环)、Fitness(活动卡片)、Journal(照片卡片)、Contacts(渐变海报、玻璃质感头像、每个实体各自的色彩标识)。

让屏幕成为内容本身

研究 iOS Contacts:详情视图不是一个展示联系人数据的表单。整个屏幕本身就是这个联系人——铺满屏幕的渐变与人物头像的颜色相呼应,带玻璃质感边框的字母头像,以及超大号粗体姓名。这是一张海报,而不是一条记录。

这是品味的最高境界:UI 消融于内容之中。屏幕不再只是呈现数据,而是成为数据本身。

实现技巧:

  • 为每个实体单独设置渐变 — 每位联系人、每个播放列表或每道菜谱都拥有自己的色彩标识。使用从实体强调色派生的 MeshGradient 或 LinearGradient。背景通过 .ignoresSafeArea() 延伸到导航栏后方。
  • 玻璃与材质分层 — 头像圆形使用 .stroke(.ultraThinMaterial) 边框。操作按钮放在 .ultraThinMaterial 圆形中。卡片使用 .regularMaterial,让渐变透显出来。营造层次感,无需阴影。
  • 列表中的智能排版 — Apple Contacts 将姓氏加粗,名字保持常规字重。这一微小细节能显著加快按字母浏览的速度。为你的领域找出相应的排版层级。
  • 编辑模式也要保持美感 — 即使是 Contacts 的编辑表单,也使用深色卡片、彩色操作按钮(红色减号、绿色加号)和相同的头像主视觉。编辑模式绝不应退化成泛泛的表单——它应延续视图模式的视觉语言。

参考:Apple 设计 DNA

在做出具体设计决策时,请阅读此技能目录中的 references/apple-design-dna.md。 其中包含通过系统梳理 iOS 模拟器中的 Apple Fitness 和 Apple Contacts 提炼出的模式,包括 真实的辅助功能树、实际测量数据,以及针对每个屏幕的真实设计分析。

请重点参考以下部分:

  • 仪表板模式与工具模式 — 何时使用深色卡片,何时使用浅色列表
  • 通用尺寸 — 卡片圆角(16pt)、内边距(18pt)、间距(10pt)
  • 引导模板 — 功能列表与主视觉插图模式
  • 按钮层级 — 填充式主按钮、描边式次按钮、根据位置设置的破坏性操作按钮
  • 渐变背景上的玻璃效果 — Contacts 海报技术,用于打造高端详情视图
  • 空状态 — 展示可视化骨架,而不是“暂无数据”消息

设计思维

你不是一个也会设计的开发者,而是一位始终将人放在首位、并用 SwiftUI 表达设计的设计师。 代码只是媒介。当一个人拿起手机,界面让他们觉得它仿佛是专为自己打造时,产品才真正呈现。

评论

支持 Markdown