| name | figma-ios-commercial-delivery |
| description | Quality bar for the **phase-2 baseline UI code** produced by this repo's Figma-to-UIKit/Swift workflow (not phase-3 production code, which is out of scope here). Covers: acceptance checklist, forbidden shortcuts, the UI-node-coverage audit, and when human input is still required. The "commercial" in the name refers to the phase-2 quality floor ("no shortcuts / no fabricated content / no missing nodes"), not to final production-ready business code — production code is produced in phase 3 by combining this baseline with PRD + API docs. Use when generating or reviewing the phase-2 Figma-based iOS UI output. |
Figma → iOS:阶段 2 基础 UI 代码 · 质量底线(验收)
📌 「商用」一词的澄清
本 skill 名字里的 commercial-delivery / 商用 指的是 阶段 2 基础 UI 代码的质量底线,不是「直接上线的最终业务代码」。
- ✅ 本 skill 管:UI 节点覆盖率 100%、无编造资源、无假 URL、无跳过规则以外的偷工、颜色/字体走 bindings、相对布局语义。
- ❌ 本 skill 不管:接口对接、ViewModel、路由、埋点、错误处理——这些是 阶段 3(PRD + 接口文档二次加工)的职责。
简单说:阶段 2 保证交给阶段 3 的「UI 地基」干净、完整、可识别;阶段 3 在此之上盖业务楼。
职责边界
- 负责:规定 阶段 2 基础 UI 代码 的最低质量标准(自检清单、禁止项、须显式声明的依赖)。
- 不负责:重复
figma-ios-design-token-mapping、figma-ios-minimum-deployment-12、figma-ios-to-code-conventions、figma-ios-snapkit-layout 的正文;以链接为准。
- 不负责:阶段 3 的任何内容(接口接入 / ViewModel / 业务逻辑 / 埋点),这些应该保留
// TODO(阶段3): ... 注释,交给阶段 3。
目标(与「来回改细节」的关系)
阶段 2 一次产出应尽量 自带 布局策略、bindings 色/字映射、可维护结构,使 UI 地基默认就接近主工程可合入水平(只剩阶段 3 的数据/业务补完)。
仍可能需要人工或第二轮的情况见文末「仍须确认」——Agent 应在代码注释或回复中 一次性列清,而不是留到用户追问。
宿主前提(不满足则必须在交付物中写明)
- 已配置
.cursor/bindings/(host.json + 三张 map);生成时按配置输出,不要在 skill/代码里强制写死某宿主 SDK import。
- 布局:按
host.layout_engine(如 snapkit);若工程无对应库,用 NSLayoutConstraint/anchor,语义仍须是相对约束。
类名前缀(强制)⚠️
所有新建的顶层 class / struct / enum / protocol / actor 必须以业务类前缀 <P> 开头(见 host.json 的 class_prefix / class_prefix_fallback,以及 PROJECT_BINDING §0)。
- ViewController →
<P>FillOrderViewController
- View →
<P>OrderHeaderView
- Cell →
<P>RequirementCell
- Model / Service / ViewModel →
<P>RequirementModel / <P>RequirementService
豁免(仅这些,不加业务前缀):
extension(跟着主类名)
- 引用宿主已有类型:
host.bases.* 里的基类名、以及 color/font/iconfont map value 里已出现的类型(只引用、不新建)
private / fileprivate 的文件内辅助类型
不必再单独列宿主类名豁免表。
交付前自检(生成或审阅时逐项打勾)
前置硬约束:本节自检与 figma-ios-codegen-workflow §「全量还原原则」 共同生效。任何一项不通过就不算交付完成,禁止以"已实现主要功能"为由跳过剩余自检。
0. 节点覆盖率对账(强制,先于其他所有自检)⭐️
视觉验收无法判断"少画了什么"——必须先用节点 ID 做结构化对账。
对账步骤:
- 从
{data_dir}/index.json 的 by_id / tree 提取应实现节点集合 S,剔除以下:
figma-ios-to-code-conventions 跳过项(Home Indicator / Status Bar / Device Frame / 设计标注)
- 父节点已生成且本节点是
_role.is_transparent_wrapper / _role.is_single_child_wrapper 的纯结构容器
- 从生成的 Swift 代码中提取已实现节点集合 G:
- 每个 view / cell / 配置块都应在代码注释里有
// Figma node: <node_id> 标记
- 用
rg "Figma node:" <output_dir> 拉取所有 node_id
- 计算
missing = S - G,输出节点覆盖率报告(强制,必须在回复正文里贴出):
节点覆盖率报告
─────────────────────────
应实现节点数:N
已实现节点数:M
覆盖率:M/N = X%
✅ 已实现(按 frame.relative.y 排序,便于对照截图):
- 1:280 AppCategoryListViewController(根容器)
- 1:291 导航栏
- 30:8689 品类 icon
...
❌ 未实现:
- 30:9001 标签组容器 原因:?
- 30:9015 价格步进器 原因:?
- 30:9020 段位选择器 原因:?
⚠️ 已豁免(属于「唯一允许的简化场景」之一,须注明依据):
- 30:9100 服务器下发图片占位(依据:场景 1,已加 TODO)
判定:
0.1 假覆盖检测(必须做,否则覆盖率报告无意义)⚠️
只在代码注释里写 // Figma node: <id> 不足以证明节点真的实现了。下面这些是「假覆盖」,必须算作未实现并补齐:
| 假覆盖模式 | 例子 | 判定 |
|---|
| 整组用切图代替含语义子节点的容器 | stepStripImageView 用一张 img_7ebee_3 代替了 6 个 STEP 文本/箭头子节点 | 子节点全部算未实现,按 figma-ios-vector-vs-code §「红线规则」 拆解 |
| hardcode 文字数组 | let titles = ["排位赛","娱乐赛","QQ区"] | 每个字符串对应的 TEXT node_id 都算未实现,必须改成读 design.json[node].text |
| 绕过映射表硬编码色 | 未查 color_map.json 就写死色值/臆造 token | 视为「样式未实现」,按 figma-ios-design-token-mapping 重做(命中 map / 未命中 fallback) |
| hardcode 尺寸/坐标 但与 design.json 不一致 | offset(49) 但 frame.relative.y == 96 | 视为「布局未实现」,按 frame.relative 修正 |
| 图标用普通 UIView 替代 iconfont 节点 | let plus = UIButton(title:"+") 代替 iconfont/icon_plus_24 | iconfont node 算未实现,按 figma-ios-iconfont-mapping 重做 |
节点级 opacity 漏读(⭐️常见事故) | design.json[341:277].opacity = 0.2033,代码里没设 view.alpha | 视为「样式未实现」,按 figma-ios-to-code-conventions §5 补 view.alpha = opacity |
| 文件顶部映射表与代码不一致 | 表里写 → 拆解,但代码里只 addSubview 了一个 ImageView | 视为不合规,必须二选一:要么补齐子节点,要么改表格说明真用了切图(同时通过红线检查) |
自检方法:
rg -o "Figma node: ([0-9I:;]+)" -r '$1' <output_file> | sort -u > /tmp/coverage_claimed.txt
rg '"[\u4e00-\u9fa5]+"' <output_file>
rg 'withHexString: "#[0-9A-Fa-f]{6}"' <output_file>
python3 -c "
import json
d = json.load(open('design.json'))
for nid, n in d['nodes'].items():
op = n.get('opacity')
if op is not None and op != 1.0:
print(f'{nid} opacity={op} name={n[\"name\"]}')
"
实现 hint:按 design.json.nodes 对账生成代码里的 // Figma node: <node_id> 注释;自动剔除 Home Indicator / Status Bar / Device Frame / 设计标注 / iconfont_library / audit.placeholder_rectangles[].must_skip 节点及其后代。LLM 不用自己数节点,只要保证每个生成的 view 都带 Figma node: 注释即可。
0.2 高风险字段对账(必须做,与 audit.json 配套)⚠️⚠️
audit.json 列出的每条 entry 都必须在生成代码里有对应处理。不查 audit = 交付不算完成。
强制对账流程:
- 读
{data_dir}/audit.json
- 对 7 类字段逐一对账:
| audit 字段 | 必须在代码中能找到 | 缺失则视为 |
|---|
non_default_opacity[].node_id | view.alpha = <opacity> 在该节点对应 view 上 | 样式未实现 |
non_solid_fills[].node_id(GRADIENT) | CAGradientLayer / applyGradient / MKGradientView 等 | 样式未实现 |
non_solid_fills[].node_id(IMAGE) | UIImage(named:) 引用 assets/ios/manifest.json 里的资源 | 资源未挂 |
non_solid_strokes[].node_id | CAShapeLayer mask + CAGradientLayer 实现渐变边框 | 边框未实现 |
with_effects[].node_id | layer.shadow* 或 UIVisualEffectView | 阴影未实现 |
non_standard_fonts[].node_id | 查 font_map;未命中则 UIFont(name:) + systemFont fallback | 字体退化 |
letter_spacing[].node_id | NSAttributedString .kern = <value> | 字距漏读 |
non_normal_blend_modes[].node_id | CALayer.compositingFilter = ... 或自绘 | 混合模式未实现 |
list_containers[].node_id(⭐红线) | UICollectionView(按 scroll_axis 选 horizontal/vertical FlowLayout) | 列表退化 |
export_asset_with_semantic_children[].node_id(⭐红线) | 拆解为子节点逐一实现,禁止用 UIImage(named:) 整组替代 | 切图吞掉子树 |
- 任何一项不通过 → 必须补,不能在「视觉差异不大」名义下放过。
自检:对照 audit.json 逐条对账。详见 figma-ios-codegen-workflow §2.0。
1. 节点
按 figma-ios-to-code-conventions 跳过 Home Indicator 等系统装饰;业务图层无多余重复。
2. 布局
主要 UI 非「整屏 frame 堆砌」;相对约束 + SnapKit(或等价的 Auto Layout);安全区、横竖屏/不同宽度下无灾难性断裂(至少 leading/trailing 有依据)。
3. 色与字
颜色与字体走 figma-ios-design-token-mapping:先查 .cursor/bindings map;仅未命中时才用 fallback 的 UIColor / UIFont;禁止臆造未在 map 中的设计系统 API。
4. 系统版本
符合 figma-ios-minimum-deployment-12;无未包裹的 iOS 13+ API。
5. 可维护
子视图职责清晰;魔法数尽量收拢为 private enum/static let 并注明对应 Figma 节点或尺寸含义。
6. 资源与网络
图片若有占位,须标注 占位 与后续替换方式;异步加载须有取消/弱引用,避免泄漏与崩溃。
7. 导航/状态栏(强制)
- 若
host.json 的 vc_required_overrides 非空:每个生成的 VC 必须按模板实现;{has_custom_nav} 按是否使用 navigation.custom_nav_class 替换
navigation.strategy == "system" → 用系统导航栏,不强制宿主钩子
- 若全屏自定义顶栏,须处理返回栈预期(避免闪栏、叠栏),与宿主约定一致时在注释中写一句。
8. 无障碍(⚠️ 当前未实现)
当前生成的代码不包含无障碍属性(accessibilityLabel / accessibilityTraits / isAccessibilityElement)。如项目有无障碍要求,需人工补充。
9. Swift / OC 互操作(按需)
仅当 host.base_is_objc == true 或用户明确要求 OC 混编时,才按下文「Swift / OC 互操作约定」加 @objc / @objcMembers。
纯 Swift / 系统基类场景不必默认加 @objcMembers,也不必写宿主 SDK 的 import。
回复纪律(禁止的措辞模式,与「全量还原原则」配套)⛔
这一节直接针对 LLM 在交付时使用客服式"礼貌降级"话术,把规范包装成偏好的反模式。
| ❌ 禁止使用 | 为什么禁止 | ✅ 正确写法 |
|---|
| 「若你希望下一步把 X / Y 也按节点 ID 全部对齐,我可以继续……」 | 把"全量还原"这一默认规范包装成"可选服务",让用户误以为缺漏是默认状态 | 「本轮已实现 A / B / C,design.json 中 D / E / F(节点 ID: ...)未实现,违反全量还原原则,现在继续补齐」 |
| 「为简化代码示例,省略了 ...」 | 简化不是默认许可的;除非属于「唯一允许的简化场景」三类 | 直接生成完整代码;上下文不足按 workflow 「未完成处置模板」声明 |
| 「当前为简化版,需要可补全」 | 同上,把规范当偏好 | 不发生这种交付;若真的截断,按未完成模板先声明再继续 |
| 「视觉差异不大」/「整体已对齐」(实际上少了节点) | 用视觉相似度掩盖结构性缺漏 | 必须先输出节点覆盖率报告,再谈视觉相似度 |
| 「这一版本未实现,如有需要可继续完善」 | 同样是把缺漏伪装成增值服务 | 「未实现节点:[列表],原因:[token/上下文限制],正在继续补齐」 |
自检触发条件:在生成回复最终文本前,必须扫描自己的回复草稿,命中上表任一句式 → 改写为右列正确写法 → 再发送。
Swift / OC 互操作约定(仅 host.base_is_objc == true 或用户要求时)
类声明
- 需要被 OC 调用时:类可加
@objcMembers;访问级别与基类一致。
- 不要无脑加
@objcMembers;纯 Swift UIKit 场景可省略。
- 不要在 skill 里强制写某宿主 SDK 的
import。
class AppCategoryListViewController: UIViewController {
var categoryRows: [CategoryRow] = []
}
- 纯 Swift 类型(
struct / enum / 泛型)不能 @objc,只在 Swift 内使用。
访问级别 + 协议一致性(生成时强制规避,禁止事后修补)
报错形态:
Method 'collectionView(_:numberOfItemsInSection:)' must be declared public
because it matches a requirement in public protocol 'UICollectionViewDataSource'
根因:类被声明为 public / open 时,实现 public 协议(UICollectionViewDataSource / UITableViewDataSource / UIScrollViewDelegate 等)的方法访问级别必须 ≥ public,而 extension 默认是 internal。
生成阶段强制流程(按基类访问级别匹配)
Step 1:在生成代码前,先探测宿主基类访问级别
宿主工程的基类(读 host.json → bases.view_controller,下称 <VC_BASE>)可能是 public、open 或 internal,三种情况生成的代码不同:
rg -n "^(public |open |@objc public |@objc open )?class\s+<VC_BASE>\b" <host_project_root>
rg -n "^@interface\s+<VC_BASE>\b" <host_project_root>
判定规则:
| 基类形态 | 生成的 Swift 类 | 协议实现 extension |
|---|
open class <VC_BASE> | open class XxxVC: <VC_BASE> | extension XxxVC: ... { open func ... } |
public class <VC_BASE> | public class XxxVC: <VC_BASE> | public extension XxxVC: ... { ... } |
class <VC_BASE>(internal) | class XxxVC: <VC_BASE> | extension XxxVC: ... { ... } |
OC @interface <VC_BASE> | class XxxVC: <VC_BASE> | extension XxxVC: ... { ... } |
| 探测失败 / 无法访问宿主源码 | 保守默认走 internal,文件顶加 NOTE | 同上 |
Step 2:基于探测结果选模板
✅ 基类是 public 时唯一正确写法(整个 extension 加 public,一次性提升所有方法):
public class AppCategoryListViewController: UIViewController { ... }
public extension AppCategoryListViewController {
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int { ... }
}
✅ 基类是 internal / OC 时(默认场景,最简洁):
class AppCategoryListViewController: UIViewController { ... }
extension AppCategoryListViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int { ... }
}
❌ 绝对禁止的反模式:
public class AppCategoryListViewController: UIViewController { ... }
extension AppCategoryListViewController: UICollectionViewDataSource {
func collectionView(_ cv: UICollectionView, numberOfItemsInSection s: Int) -> Int { ... }
}
自检(生成完成后回头看一次)
重写 OC 基类方法
若基类为 OC(host.base_is_objc),其可重写方法,Swift 子类重写时必须显式加 @objc(即使类已 @objcMembers,重写场景仍需要),见 figma-ios-navigation。
OC 互操作自检
禁止项(默认不允许出现在「交付」里)
// TODO / FIXME 且无替代方案说明。
- 整页业务布局仅靠
layoutSubviews 里写死 frame(无约束)作为 唯一 手段。
- 绕过 bindings map 臆造设计系统 API,或未命中时不用 fallback。
- 无条件使用 SF Symbols、
UIStatusBarStyle.darkContent、CALayerCornerCurve 等 未 按 figma-ios-minimum-deployment-12 处理。
仍须产品/宿主确认(须在交付时一次性写清)
- 接口、文案、埋点、路由、权限、多语言。
- 设计稿未给出的 空态 / 错误态 / 加载态。
- 宿主 未 声明 layout_engine / 导航策略等——须在注释或回复中列出假设。
- 无障碍属性:当前未自动生成,如项目有 VoiceOver/无障碍要求,需人工补充或等待
figma-accessibility Skill 实现(见 MAINTAINER_GUIDE.md P1 第 3 条)。
相关