From apple-craft
Pencil .pen 디자인을 SwiftUI/UIKit 뷰 코드로 단계별 구현 — 컴포넌트 분석, 토큰 매핑, 순차 구현, 시각 검증. "디자인 구현", "pen to code", "pen-craft", "디자인에서 코드", "뷰 구현", "디자인 코드", "pen 파일", "pen to swiftui", "design implementation", "디자인 변환", "pen 구현", "pencil 구현", "Pencil에서 SwiftUI", "디자인 코드로", "pen에서 코드", "디자인 to 코드", "코드로 변환" 요청 시 활성화
How this skill is triggered — by the user, by Claude, or both
Slash command
/apple-craft:pen-craftThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<example>
Pencil MCP의 권장 단계적 절차를 따라 .pen 디자인을 SwiftUI/UIKit 뷰 코드로 구현합니다.
단순 변환이 아닌, 컴포넌트 분석 → 토큰 추출 → 순차 구현 → 시각 검증의 전체 파이프라인을 수행합니다.
Pencil MCP 연결 필수. 미연결 시 연결 안내 후 종료합니다.
pen-craft는 대상 범위에 따라 자동으로 모드를 결정합니다:
| 모드 | 대상 | 절차 |
|---|---|---|
| component | 특정 컴포넌트 1개 | Phase 1 → 3 (해당 컴포넌트만) |
| screen | 화면 프레임 1개 | Phase 1 → 2 → 3 → 4 → 5 |
| full | .pen 파일 전체 | Phase 0 → 1 → 2 → 3 → 4 → 5 (모든 화면) |
get_editor_state()
사용자가 경로를 지정한 경우:
open_document(filePathOrNew: "경로.pen")
미지정 시:
get_editor_state()의 활성 문서 사용Glob: **/*.pen으로 프로젝트 내 .pen 파일 탐색batch_get(patterns: [{type: "frame", readDepth: 2}])
→ 최상위 프레임 목록 (화면 단위) 확인. 사용자가 특정 프레임을 지정하지 않았으면 목록을 보여주고 선택 요청.
get_guidelines(category: "code")
→ Pencil의 최신 코드 생성 가이드라인을 확인하여 Phase 3에서 준수.
Pencil 핵심 원칙: "Only process components that appear in the current frame."
batch_get(nodeIds: ["프레임ID"], readDepth: 10)
→ 완전한 노드 트리. 각 노드의 type, name, properties, children 파악.
노드 트리에서 componentId가 있는 노드를 찾아 컴포넌트 목록을 작성합니다:
| 컴포넌트 | 인스턴스 수 | override 유형 |
|---|---|---|
| CardItem | 3 | text, icon, color |
| ActionButton | 2 | label, style |
각 컴포넌트의 대표 인스턴스에 대해:
get_screenshot(nodeId: "인스턴스ID")
→ 시각적 참조 이미지 확보. Phase 3에서 구현 결과와 비교.
컴포넌트 간 중첩 관계를 분석하여 leaf → parent 순서를 결정:
1. IconBadge (leaf — 다른 컴포넌트에 의존 없음)
2. CardItem (IconBadge를 포함)
3. CardSection (CardItem을 포함)
→ 이 순서가 Phase 3의 구현 순서.
get_variables()
→ .pen 파일에 정의된 모든 디자인 토큰 (색상, 폰트, 간격, 반경 등).
search_all_unique_properties(patterns: [{type: "frame"}, {type: "text"}])
→ 프레임 전체에서 사용된 모든 고유 속성값 수집. 토큰으로 매핑되지 않은 하드코딩 값 탐지.
추출된 토큰을 Swift 코드로 변환합니다.
SwiftUI 타겟:
// DesignTokens.swift
import SwiftUI
// MARK: - Colors
extension Color {
static let designBackground = Color("bg") // $bg
static let designSurface = Color("surface") // $surface
static let designAccent = Color("accent") // $accent
static let designTextPrimary = Color("textPrimary")
static let designTextSecondary = Color("textSecondary")
}
// MARK: - Typography
extension Font {
static let designLargeTitle: Font = .largeTitle // $font-largeTitle
static let designTitle: Font = .title2 // $font-title
static let designBody: Font = .body // $font-body
static let designCaption: Font = .caption // $font-caption
}
// MARK: - Spacing
enum DesignSpacing {
static let xs: CGFloat = 4 // $spacing-xs
static let sm: CGFloat = 8 // $spacing-sm
static let md: CGFloat = 12 // $spacing-md
static let lg: CGFloat = 16 // $spacing-lg
static let xl: CGFloat = 24 // $spacing-xl
static let xxl: CGFloat = 32 // $spacing-xxl
}
// MARK: - Corner Radius
enum DesignRadius {
static let card: CGFloat = 12 // $radius-card
static let button: CGFloat = 8 // $radius-button
static let input: CGFloat = 6 // $radius-input
}
UIKit 타겟: UIColor/UIFont extension으로 동일 패턴 생성.
토큰 매핑 테이블 (내부 기록용):
| Pencil 토큰 | SwiftUI | UIKit |
|---|---|---|
| $bg | Color.designBackground | UIColor.designBackground |
| $accent | Color.designAccent | UIColor.designAccent |
| $font-body | Font.designBody | UIFont.designBody |
| $spacing-lg | DesignSpacing.lg | DesignSpacing.lg |
| $radius-card | DesignRadius.card | DesignRadius.card |
모든 값은 토큰에서만 참조. 하드코딩 금지.
UIKit 타겟 시 추가:
extension UIColor {
static let designBackground = UIColor(named: "bg")!
// ...
}
Asset Catalog 연동:
프로젝트에 .xcassets가 있으면 Color Set도 함께 생성합니다.
Pencil 핵심 원칙: "Process components ONE AT A TIME (extract → recreate → validate → next)."
Phase 1-4에서 결정한 의존성 순서대로, 한 번에 하나의 컴포넌트만 처리합니다.
batch_get(nodeIds: ["컴포넌트ID"], readDepth: 10)
→ 해당 컴포넌트의 완전한 노드 트리. 자식 노드, 레이아웃, 속성 모두 포함.
해당 컴포넌트의 모든 인스턴스를 읽어 override 패턴을 파악:
batch_get(nodeIds: ["인스턴스1", "인스턴스2", "인스턴스3"])
→ 어떤 속성이 인스턴스마다 다른지 → Swift 프로퍼티로 노출할 필드 결정.
매핑 규칙:
SwiftUI 변환 규칙:
| Pencil 속성 | SwiftUI |
|---|---|
| layout: "vertical" | VStack(spacing:) |
| layout: "horizontal" | HStack(spacing:) |
| layout: "grid" | LazyVGrid / LazyHGrid |
| width: "fill_container" | .frame(maxWidth: .infinity) |
| height: "fill_container" | .frame(maxHeight: .infinity) |
| width: "fit_content" | 기본값 (SwiftUI 자동 사이징) |
| padding: [top, right, bottom, left] | .padding(EdgeInsets(...)) |
| gap: N | VStack/HStack spacing 파라미터 |
| fill: "$token" | .background(Color.designXxx) |
| cornerRadius: N | .clipShape(RoundedRectangle(cornerRadius:)) |
| stroke / border | .overlay(RoundedRectangle(...).stroke(...)) |
| opacity: N | .opacity(N) |
| shadow | .shadow(color:radius:x:y:) |
| type: "text" | Text("...").font(.designXxx) |
| type: "image" | Image(systemName:) 또는 Image("asset") |
| overflow: "scroll" | ScrollView { ... } |
UIKit 변환 규칙:
| Pencil 속성 | UIKit |
|---|---|
| layout: "vertical" | UIStackView(axis: .vertical) |
| layout: "horizontal" | UIStackView(axis: .horizontal) |
| width: "fill_container" | constraint: widthAnchor == superview |
| padding | layoutMargins / directionalLayoutMargins |
| gap | UIStackView.spacing |
| fill: "$token" | backgroundColor = .designXxx |
| cornerRadius | layer.cornerRadius |
코드 작성 원칙:
batch_get(includePathGeometry: true)로 추출빌드 도구 폴백 체인:
BuildProject) — 최우선xcodebuild + xcsift -E — CLI 폴백swift build — SPM 프로젝트빌드 실패 시 에러를 읽고 수정 후 재시도 (최대 3회).
Pencil 디자인 스크린샷 확보 (Phase 1-3에서 이미 캡처):
get_screenshot(nodeId: "컴포넌트ID")
(선택) 시뮬레이터에서 구현 결과 스크린샷:
screenshot_app 또는 screenshotRenderPreview두 이미지를 비교하여 불일치 확인:
불일치 발견 시 → 코드 수정 → Step 3-D로 돌아가 재검증
Pencil 핵심 원칙: "Only proceed to next component when current is perfect."
현재 컴포넌트가 디자인과 일치할 때만 다음 컴포넌트로 진행. 진행 상황 보고:
✅ IconBadge (1/3) — 완료
🔄 CardItem (2/3) — 구현 중
⬜ CardSection (3/3) — 대기
모든 컴포넌트가 구현되면 전체 화면을 조립합니다.
batch_get(nodeIds: ["프레임ID"], readDepth: 10)
→ 최신 프레임 구조 재확인. Phase 1 이후 변경이 있을 수 있음.
프레임 내 모든 컴포넌트 인스턴스의 override를 수집:
batch_get(nodeIds: ["인스턴스1", "인스턴스2", ...])
→ 각 인스턴스의 props (텍스트, 아이콘, 색상 등) 확정.
프레임의 레이아웃 구조를 SwiftUI View로 변환:
struct SettingsView: View {
var body: some View {
ScrollView {
VStack(spacing: DesignSpacing.lg) {
// 컴포넌트 인스턴스 배치 — override 값 전달
CardItem(
title: "알림",
icon: "bell.fill",
style: .accent
)
CardItem(
title: "개인정보",
icon: "lock.fill",
style: .default
)
}
.padding(EdgeInsets(
top: DesignSpacing.md,
leading: DesignSpacing.lg,
bottom: DesignSpacing.xl,
trailing: DesignSpacing.lg
))
}
.background(Color.designBackground)
}
}
화면 컨테이너 매핑:
| 디자인 패턴 | SwiftUI 컨테이너 |
|---|---|
| 스크롤 가능한 목록 | ScrollView + LazyVStack |
| 탭 기반 네비게이션 | TabView |
| 내비게이션 스택 | NavigationStack |
| 모달/시트 | .sheet / .fullScreenCover |
| 그리드 레이아웃 | LazyVGrid |
Pencil 핵심 원칙: "Count component instances in design vs implementation."
디자인 내 인스턴스 수와 코드 내 사용 횟수를 비교:
| 컴포넌트 | 디자인 인스턴스 | 코드 사용 | 일치 |
|---|---|---|---|
| CardItem | 3 | 3 | ✅ |
| ActionButton | 2 | 2 | ✅ |
디자인:
get_screenshot(nodeId: "프레임ID")
구현: baepsae 또는 Xcode MCP로 시뮬레이터 스크린샷.
| 항목 | 확인 |
|---|---|
| 모든 컴포넌트 인스턴스가 코드에 존재 | |
| 모든 override 값이 정확히 반영 | |
| 색상이 디자인 토큰과 일치 | |
| 타이포그래피 (폰트, 크기, 굵기) 일치 | |
| 간격 (padding, gap, margin) 일치 | |
| 레이아웃 방향/정렬 일치 | |
| 코너 라디우스 일치 | |
| 스크롤/오버플로우 동작 정상 | |
| fill_container 요소가 정상 확장 | |
| 하드코딩된 값 없음 (모두 토큰 참조) |
불일치 항목이 있으면 해당 컴포넌트의 Step 3로 돌아가 수정. 최종 통과 시 완료 보고.
## pen-craft 완료
| 항목 | 값 |
|------|-----|
| .pen 파일 | <파일명> |
| 대상 프레임 | <프레임명> |
| 컴포넌트 수 | N개 |
| 생성 파일 | DesignTokens.swift, View 파일 N개 |
| 시각 검증 | PASS / 수동 확인 필요 |
### 생성된 파일 목록
- `DesignTokens.swift` — 디자인 토큰 (색상, 폰트, 간격, 반경)
- `IconBadge.swift` — 아이콘 뱃지 컴포넌트
- `CardItem.swift` — 카드 아이템 컴포넌트
- `SettingsView.swift` — 설정 화면 조립
get_guidelines(category: "code")를 반드시 호출하여 최신 권장사항을 확인design-coder 에이전트에 위임full 모드에서 여러 화면을 처리할 때, design-coder 에이전트에 화면별 구현을 위임할 수 있습니다:
Agent 도구 호출:
description: "design-coder: {화면명} SwiftUI 구현"
subagent_type: "apple-craft:design-coder"
prompt: |
.pen 파일: {경로}
대상 프레임: {프레임 ID}
토큰 파일: {DesignTokens.swift 경로}
프레임워크: SwiftUI (또는 UIKit)
출력 디렉토리: {경로}
Pencil 가이드라인을 준수하여 컴포넌트 분석 → 순차 구현 → 시각 검증을 진행하세요.
npx claudepluginhub oozoofrog/oozoofrog-plugins --plugin apple-craftConverts Stitch mobile designs to native iOS SwiftUI views with VStack/HStack/ZStack layout mapping, dark mode color assets, NavigationStack/TabView routing, and Xcode project structure.
Translates between Figma designs and SwiftUI code in both directions: design-to-code and code-to-design. Routes to direction-specific reference docs.
Converts Claude HTML/CSS prototypes (via design URL or .tar.gz) to single self-contained SwiftUI View files in active Xcode workspaces. Generates code, builds, previews, and visually diffs for layout fidelity.