关于 stitch-swiftui-components
此技能将 Stitch、 本地 HTML 或 URL 中的移动端布局转为原生 SwiftUI 视图,面向 iOS 开发者。内容涵盖来源检查、设计令牌提取、布局映射和 Xcode 项目结构。
主要功能
- 01将移动布局转换为 SwiftUI
- 02支持 Stitch、HTML 或 URL 来源
- 03将 HTML 与 CSS 模式映射为视图
- 04提取设计令牌
- 05规划 Xcode 应用结构
适用场景
- 01根据 Stitch 屏幕构建原生 iOS 视图
- 02将移动端 HTML 页面转换为 SwiftUI
- 03依据移动网页创建 SwiftUI 屏幕
MCP 相关
处理 Stitch 来源时,技能会通过 Stitch MCP 发现工具并获取屏幕元数据;本地 HTML 和 URL 来源无需 MCP。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ gabelul/ stitch-kit --skill stitch-swiftui-components安装后在 Claude Code 中输入/stitch-swiftui-components确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
resources/architecture-checklist.mdcomponent-template.swiftlayout-mapping.mdscripts/fetch-stitch.sh详细介绍
Stitch → SwiftUI(原生 iOS)
你是一名 Swift/SwiftUI 工程师。你可以将移动端 UI 布局——使用 deviceType: MOBILE 生成的 Stitch 屏幕、本地 HTML 文件或 URL——转换为原生 iOS SwiftUI 视图,即可在 Xcode 中构建并运行的 .swift 文件。你遵循 Apple 人机界面指南,编写出符合 iOS 风格的代码。
何时使用此技能
在以下情况下使用此技能:
- 用户希望基于现有设计生成原生 iOS输出
- 用户提到“SwiftUI”“Xcode”“iOS”或“原生 iOS 应用”
- 源内容是移动端布局——窄屏、纵向排列、适合触控的目标(使用
deviceType: MOBILE生成的 Stitch 屏幕,或呈现为移动端布局的本地文件/URL)
**注意:**此技能面向使用 SwiftUI 的 iOS 16+。若要开发跨平台应用(iOS + Android),请改用 stitch-react-native-components。
前提条件
移动端布局源文件仅作为结构和视觉参考——其中的任何内容都不会直接交付,SwiftUI 视图将从头编写。以下任意一种来源均可:
- Stitch 屏幕——需要访问 Stitch MCP,并且屏幕须使用
deviceType: MOBILE生成 - 移动端布局的本地 HTML 文件——无需 Stitch 账号
- 呈现移动端布局的 URL——无需 Stitch 账号
无论采用哪种方式,桌面端布局都无法很好地映射到 SwiftUI,除非进行大量重新设计。
此外还需要:
- macOS 上的 Xcode 15+
- Swift 5.9+
第 1 步:确定源文件
后续所有步骤都将读取同一个文件:temp/source.html。根据用户提供的来源,将 HTML 获取到该文件中,然后继续第 2 步——无论标记来自哪里,本技能的其余步骤都相同。
本技能仅适用于移动端布局——窄屏、纵向排列、适合触控的目标。无论来源是什么,桌面端布局都无法很好地映射到 SwiftUI,除非进行大量重新设计。确认是否为移动端布局的方式取决于 HTML 的来源:
- Stitch 屏幕——确认它是使用
deviceType: MOBILE生成的。如果截图显示的是桌面端布局,请停止并告知用户先使用deviceType: MOBILE重新生成。 - 本地 HTML 文件或 URL——检查标记:是否包含
<meta name="viewport">标签、移动端优先的媒体查询、根容器是否设置了较窄的max-width,以及点击目标是否适合触控。如果明显是桌面端布局(宽屏多列网格、仅悬停交互、没有 viewport 元信息),请停止并告知用户该来源不是移动端布局——不要让他们“使用deviceType: MOBILE重新生成”,因为这条指示不适用于 Stitch 以外的来源。
来自 Stitch 屏幕:
- 发现命名空间——使用
list_tools查找 Stitch MCP 前缀 - 获取元数据——使用
[prefix]:get_screen获取设计 JSON - 下载 HTML——GCS URL 需要使用可靠的下载器:
bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html" - 视觉审核——转换前检查
screenshot.downloadUrl。在该 URL 后追加=s0以获取完整分辨率;直接使用原 URL 时,无论 API 报告的width/height是多少,都会返回 512px 缩略图。按照上文所述确认这是移动端布局。
来自本地 HTML 文件:
mkdir -p temp && cp "path/to/design.html" temp/source.html
打开文件,并按照上文所述确认这是移动端布局。
来自 URL:
bash scripts/fetch-stitch.sh "https://example.com/page" "temp/source.html"
尽管名称如此,该脚本是通用的加固型下载器——它会跟随重定向、重试临时故障、处理 gzip,并在结果为空时明确报错。它不关心 URL 是否指向 Stitch。按照上文所述确认页面是移动端布局。
**来自截图:**没有上传途径——Stitch MCP API 不提供图像上传工具。你可以通过 stitch-mcp-generate-screen-from-text,根据文本提示词重新创建设计;也可以手动编写 HTML,然后使用上文的本地文件途径。
只有 Stitch 途径需要 API 密钥。转换本地文件或 URL 完全不需要 Google 账号。
第 2 步:Xcode 项目结构
MyApp/
├── MyApp.swift ← @main entry point
├── ContentView.swift ← Root view (TabView or NavigationStack)
├── Theme/
│ ├── ThemeTokens.swift ← Design token constants
│ └── Color+App.swift ← Color extension with semantic names
├── Views/
│ ├── [ScreenName]View.swift ← One file per screen
│ └── Components/
│ └── [Name]View.swift ← Reusable component views
├── Models/
│ └── MockData.swift ← Static preview data
└── Assets.xcassets/
└── Colors/ ← Color assets for light/dark mode
第 3 步:HTML/CSS → SwiftUI 布局映射
这是核心转换步骤。对源 HTML 中的每个元素应用以下规则:
布局容器
| HTML/CSS 模式 | → SwiftUI |
|---|---|
display:flex; flex-direction:column | VStack(alignment: .leading, spacing: 16) |
display:flex; flex-direction:row | HStack(alignment: .center, spacing: 12) |
display:flex; justify-content:space-between | HStack { Spacer() } 模式 |
position:absolute 覆盖层 | 使用分层视图的 ZStack |
display:grid(2 列) | LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) |
overflow-y: scroll | ScrollView(.vertical, showsIndicators: false) |
| 重复的项目列表 | List,或在 ScrollView + LazyVStack 中使用 ForEach |
position:fixed 底部导航 | TabView(首选),或使用 Spacer() 的显式 VStack |
间距映射
SwiftUI 使用点(非 Retina 屏幕上 1pt ≈ 1dp,Retina @2x 屏幕上为 2px):
// Spacing from Tailwind → SwiftUI points
// p-1(4px)→4 p-2(8px)→8 p-3(12px)→12 p-4(16px)→16
// p-6(24px)→24 p-8(32px)→32 p-12(48px)→48 p-16(64px)→64
几何映射
// Tailwind rounded- → SwiftUI cornerRadius
// rounded-sm → .cornerRadius(4)
// rounded-md → .cornerRadius(8)
// rounded-lg → .cornerRadius(12)
// rounded-xl → .cornerRadius(16)
// rounded-full → .clipShape(Capsule()) or .cornerRadius(9999)
内容元素
| HTML | → SwiftUI |
|---|---|
<p>、<span>、文本 | Text("content") |
<h1> | Text("title").font(.largeTitle).fontWeight(.bold) |
<h2> | Text("title").font(.title2).fontWeight(.semibold) |
<h3> | Text("title").font(.headline) |
<p> 正文 | Text("body").font(.body) |
<small> / 说明文字 | Text("caption").font(.caption).foregroundStyle(.secondary) |
<img> | 远程图片使用 AsyncImage(url: URL(string: "...")),资源图片使用 Image("name") |
<button> 主要按钮 | Button("Label") { action() }.buttonStyle(.borderedProminent) |
<button> 次要按钮 | Button("Label") { action() }.buttonStyle(.bordered) |
<button> 无边框/文本按钮 | Button("Label") { action() }.buttonStyle(.plain) |
<input type="text"> | TextField("Placeholder", text: $binding) |
<input type="password"> | SecureField("Password", text: $binding) |
<select> | Picker("Label", selection: $binding) { ForEach(...) } |
<toggle> / 复选框 | Toggle("Label", isOn: $binding) |
| 仅含图标的按钮 | Button { action() } label: { Image(systemName: "xmark") } |
导航模式
| 模式 | SwiftUI 实现 |
|---|---|
| 底部标签栏 | TabView,并使用 tabItem { Label("Home", systemImage: "house") } |
| 堆栈导航 | NavigationStack { ... NavigationLink(destination: ...) } |
| 模态视图 / 工作表 | .sheet(isPresented: $showModal) { ModalView() } |
| 全屏模态视图 | .fullScreenCover(isPresented: $show) { FullView() } |
| 返回导航 | NavigationStack 自动处理 |
| 操作表 | .confirmationDialog("Title", isPresented: $show) { ... } |
第 4 步:SwiftUI 中的设计令牌
按以下顺序,从 HTML 实际使用的令牌源中解析下方的十六进制值:
<head>中的内联tailwind.config(Stitch 生成的内容)——若存在,直接使用。- CSS 自定义属性(
:root { --color-primary: ... })——手写和模板 HTML 中很常见。 - 链接的或内联的样式表——解析其中声明的颜色、字体系列、圆角和间距。
- 最后手段——根据标记中出现频率最高的计算值推导令牌(主导背景色、文本颜色、强调色、标题/正文字体、边框圆角),并告知用户这是推断结果,以便他们进行修正。
URL 路由只会下载单个 HTML 响应——外部链接的样式表可能不会随之下载。如果上述方法都无法解析某个令牌,请如实说明,不要自行编造配色方案。
颜色扩展(语义令牌)
// Theme/Color+App.swift
import SwiftUI
extension Color {
// Extract these hex values using the token fallback chain above
// Backgrounds
static let appBackground = Color("AppBackground") // Asset catalog
static let appSurface = Color("AppSurface")
// Brand
static let appPrimary = Color("AppPrimary")
static let appPrimaryFg = Color("AppPrimaryForeground")
// Text
static let appText = Color("AppText")
static let appTextMuted = Color("AppTextMuted")
// Borders
static let appBorder = Color("AppBorder")
}
颜色资源目录(浅色 + 深色)
在 Assets.xcassets/Colors/ 中创建具名 Color Set:
对于每种颜色(例如 AppPrimary):
- 任意外观:
#6366F1(浅色模式下的值) - 深色外观:
#818CF8(适用于深色背景的较浅色调)
也可以通过编程方式定义(无需资源目录):
// Theme/ThemeTokens.swift
import SwiftUI
struct ThemeTokens {
let background: Color
let surface: Color
let primary: Color
let primaryFg: Color
let text: Color
let textMuted: Color
let border: Color
static let light = ThemeTokens(
background: Color(hex: "#FFFFFF"),
surface: Color(hex: "#F4F4F5"),
primary: Color(hex: "#6366F1"),
primaryFg: Color(hex: "#FFFFFF"),
text: Color(hex: "#09090B"),
textMuted: Color(hex: "#71717A"),
border: Color(hex: "#E4E4E7")
)
static let dark = ThemeTokens(
background: Color(hex: "#09090B"),
surface: Color(hex: "#18181B"),
primary: Color(hex: "#818CF8"), // Lightened for dark bg
primaryFg: Color(hex: "#09090B"),
text: Color(hex: "#FAFAFA"),
textMuted: Color(hex: "#A1A1AA"),
border: Color(hex: "#27272A")
)
}
// Convenience: Color from hex string
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let r = Double((int & 0xFF0000) >> 16) / 255
let g = Double((int & 0x00FF00) >> 8) / 255
let b = Double(int & 0x0000FF) / 255
self.init(red: r, green: g, blue: b)
}
}
基于环境的主题访问
// Anywhere in a view — automatic dark mode
@Environment(\.colorScheme) var colorScheme
var theme: ThemeTokens {
colorScheme == .dark ? .dark : .light
}
// Usage
Text("Hello")
.foregroundStyle(theme.text)
.background(theme.surface)
第 5 步:组件模板
// Views/Components/StitchComponentView.swift
import SwiftUI
/// StitchComponent — [describe purpose in one sentence]
struct StitchComponentView: View {
// MARK: - Properties (equivalent to props)
let title: String
var description: String = ""
var onAction: (() -> Void)? = nil
// MARK: - Environment
@Environment(\.colorScheme) private var colorScheme
private var theme: ThemeTokens {
colorScheme == .dark ? .dark : .light
}
// MARK: - State
@State private var isPressed = false
// MARK: - Body
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.headline)
.foregroundStyle(theme.text)
if !description.isEmpty {
Text(description)
.font(.subheadline)
.foregroundStyle(theme.textMuted)
}
if let action = onAction {
Button("Action", action: action)
.buttonStyle(.borderedProminent)
.tint(theme.primary)
}
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(theme.surface)
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(theme.border, lineWidth: 1)
)
// Minimum touch target — 44pt Apple HIG requirement
.frame(minHeight: 44)
}
}
// MARK: - Preview
#Preview {
VStack(spacing: 16) {
StitchComponentView(title: "Card Title", description: "Supporting text")
StitchComponentView(title: "With Action", description: "Tap the button", onAction: {})
}
.padding()
}
第 6 步:主应用入口
// MyApp.swift
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// ContentView.swift — root with TabView
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person")
}
}
}
}
第 7 步:SwiftUI 中的无障碍功能
SwiftUI 会自动处理其中许多内容,但务必进行验证:
// Image accessibility
Image("hero-photo")
.accessibilityLabel("Team collaborating in a modern office")
// Decorative images (screen reader skips)
Image(decorative: "background-pattern")
// Buttons — label is automatic if using Text inside
Button("Sign In") { ... } // VoiceOver reads "Sign In, button"
// Custom accessibility label when button label is ambiguous
Button { deleteItem() } label: {
Image(systemName: "trash")
}
.accessibilityLabel("Delete item")
// Group elements (treats as single unit)
VStack {
Text("Sarah Johnson")
Text("Product Designer")
}
.accessibilityElement(children: .combine)
// Dynamic type support — always use semantic fonts
Text("Headline")
.font(.headline) // ✅ Scales with user's text size
// NOT .font(.system(size: 17, weight: .semibold)) // ❌ Fixed size
步骤 8:SwiftUI 动画
SwiftUI 内置了出色的动画功能——可用于微交互:
// Button press spring
Button(action: primaryAction) {
Text("Get Started")
.padding(.horizontal, 24)
.padding(.vertical, 14)
.background(theme.primary)
.foregroundStyle(theme.primaryFg)
.clipShape(Capsule())
.scaleEffect(isPressed ? 0.96 : 1.0)
.animation(.spring(response: 0.2, dampingFraction: 0.6), value: isPressed)
}
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
// Card appear transition
VStack { /* card content */ }
.transition(.move(edge: .bottom).combined(with: .opacity))
// Respect reduced motion
@Environment(\.accessibilityReduceMotion) var reduceMotion
var animation: Animation {
reduceMotion ? .none : .spring(response: 0.3, dampingFraction: 0.7)
}
执行步骤
- 验证源内容是否为移动端布局(参见步骤 1)
- 创建 Xcode 项目 — 文件 → 新建 → App,选择 SwiftUI 界面和 Swift 语言
- 数据层 — 根据设计中的静态内容创建
Models/MockData.swift - 主题 — 创建
Theme/ThemeTokens.swift,填入提取的十六进制颜色值,并创建Color+App.swift - 组件 — 将 HTML 章节逐个文件转换为 SwiftUI 视图
- 导航 — 配置
TabView(标签栏)或NavigationStack(堆栈) - 构建并运行 — 在 Xcode 中按 Cmd+R。在浅色和深色模式下都进行测试(在 Simulator 中按 ⌃⌘A 可切换外观)
故障排除
| 问题 | 修复方法 |
|---|---|
| 视图溢出屏幕 | 添加 .frame(maxWidth: .infinity),并在父视图中添加 ScrollView |
| 文本意外截断 | 添加 .lineLimit(nil) 或 .fixedSize(horizontal: false, vertical: true) |
| 深色模式下颜色显示不正确 | 确保 Assets.xcassets 中的 Color Set 已设置深色外观 |
| 图片未加载 | 对于 AsyncImage,检查 URL 是否有效。对于本地图片,文件必须位于 Assets.xcassets 中 |
| TabView 项目不显示标签 | 内容必须直接放在 .tabItem { } 中,不能用其他视图包裹 |
| Sheet 无法关闭 | 添加 @Environment(\.dismiss) var dismiss,并在 sheet 中调用 dismiss() |
| 预览崩溃 | 检查 #Preview 是否有有效的模拟数据——解包可选值时务必提供回退值 |
参考资料
resources/component-template.swift— SwiftUI 视图样板resources/layout-mapping.md— 完整的 HTML/CSS → SwiftUI 参考resources/architecture-checklist.md— 发布前检查清单scripts/fetch-stitch.sh— 可靠的 GCS HTML 下载器