mcpField
S
stitch-swiftui-components

stitch-swiftui-components

github.com/gabelul開発4744 回インストール14 週間前に更新
リポジトリ

stitch-swiftui-components について

Stitch、ローカル HTML、URL のモバイルレイアウトを、iOS 向けのネイティブ SwiftUI ビューに変換するスキルです。ソース確認、デザイントークン抽出、レイアウト対応付け、Xcode のプロジェクト構成を案内します。

主な機能

  1. 01モバイルレイアウトを SwiftUI に変換
  2. 02Stitch、HTML、URL に対応
  3. 03HTML/CSS のパターンをビューに対応付け
  4. 04デザイントークンの抽出
  5. 05Xcode アプリ構成の案内

活用シーン

  1. 01Stitch の画面からネイティブ iOS ビューを作成
  2. 02モバイル HTML ページを SwiftUI に変換
  3. 03モバイル Web ページから 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.md
component-template.swift
layout-mapping.md
scripts/
fetch-stitch.sh

詳細

Stitch → SwiftUI(ネイティブ iOS)

あなたは Swift/SwiftUI エンジニアです。モバイル UI レイアウト(deviceType: MOBILE で生成された Stitch 画面、ローカル HTML ファイル、または URL)を、Xcode でビルドして実行できるネイティブ iOS SwiftUI ビュー(.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 以降

Step 1: ソースを解決する

後続の処理で読み込むファイルは temp/source.html の 1 つだけです。ユーザーから渡されたものに応じた方法で HTML をここに取得したら、Step 2 に進んでください。マークアップの取得元にかかわらず、このスキルの残りの手順は同じです。

このスキルが対応するのは、幅が狭く縦長で、タップしやすいサイズのターゲットを持つモバイルレイアウトのみです。ソースをどの方法で取得した場合でも、デスクトップレイアウトを SwiftUI に適切に変換するには大幅な再検討が必要です。HTML の取得元によって、モバイル向けかどうかの確認方法は異なります。

  • Stitch 画面 — deviceType: MOBILE で生成されたことを確認します。スクリーンショットがデスクトップレイアウトを示している場合は、処理を中止し、まず deviceType: MOBILE で再生成するようユーザーに伝えてください。
  • ローカル HTML ファイルまたは URL — マークアップを確認します。<meta name="viewport"> タグ、モバイルファーストのメディアクエリ、ルートコンテナの狭い max-width、タップしやすいサイズのタップターゲットが目印です。幅の広い複数列グリッド、ホバーのみの操作、viewport メタタグなしなど、明らかにデスクトップレイアウトの場合は処理を中止し、ソースがモバイルレイアウトではないことをユーザーに伝えてください。「deviceType: MOBILE で再生成してください」とは伝えないでください。Stitch 以外では意味のない指示です。

Stitch 画面の場合:

  1. 名前空間の確認 — list_tools を使って Stitch MCP のプレフィックスを見つけます
  2. メタデータの取得 — [prefix]:get_screen でデザイン JSON を取得します
  3. HTML のダウンロード — GCS URL には信頼性の高いダウンローダーが必要です。
    bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"
    
  4. 見た目の確認 — 変換前に 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 を手書きして上記のローカルファイルの手順を使ってください。

API キーが必要なのは Stitch を使う場合のみです。ローカルファイルや URL の変換に Google アカウントは必要ありません。

Step 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

Step 3: HTML/CSS から SwiftUI レイアウトへのマッピング

ここが変換の中心です。ソース HTML の各要素に、以下のルールを適用してください。

レイアウトコンテナー
HTML/CSS パターン→ SwiftUI
display:flex; flex-direction:columnVStack(alignment: .leading, spacing: 16)
display:flex; flex-direction:rowHStack(alignment: .center, spacing: 12)
display:flex; justify-content:space-betweenHStack { Spacer() } パターン
position:absolute オーバーレイレイヤー化したビューを使う ZStack
display:grid(2 列)LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16)
overflow-y: scrollScrollView(.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 での実装
下部タブバーtabItem { Label("Home", systemImage: "house") } を備えた TabView
スタックナビゲーションNavigationStack { ... NavigationLink(destination: ...) }
モーダル/シート.sheet(isPresented: $showModal) { ModalView() }
全画面モーダル.fullScreenCover(isPresented: $show) { FullView() }
戻るナビゲーションNavigationStack で自動処理
アクションシート.confirmationDialog("Title", isPresented: $show) { ... }

ステップ 4:SwiftUI のデザイントークン

以下の 16 進数値は、HTML に実際に含まれるトークンソースから、次の順に解決してください。

  1. <head> 内のインライン tailwind.config(Stitch が出力するもの)—存在する場合はそのまま使用します。
  2. CSS カスタムプロパティ(:root { --color-primary: ... })—手書きの HTML やテンプレート化された HTML で一般的です。
  3. リンクされた、またはインラインのスタイルシート —宣言された色、フォントファミリー、角丸、余白を解析します。
  4. 最終手段 —マークアップ内で最も頻繁に使われている計算済みの値(背景の基調色、テキスト色、アクセント色、見出し/本文のフォント、角丸)からトークンを推定し、推定した内容をユーザーに伝えて、修正できるようにします。

URL ルートでダウンロードされるのは単一の HTML レスポンスのみです。外部リンクされたスタイルシートが取得できるとは限りません。上記のいずれからもトークンを解決できない場合は、パレットを作り出さず、その旨を伝えてください。

Color 拡張(セマンティックトークン)
// 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")
}
Color アセットカタログ(ライト+ダーク)

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. 確認 — ソースがモバイルレイアウトであることを確認します(ステップ 1 を参照)
  2. Xcode プロジェクトを作成 — File → New → App を選択し、SwiftUI インターフェースと Swift 言語を指定します
  3. データレイヤー — デザイン内の静的コンテンツから Models/MockData.swift を作成します
  4. テーマ — 抽出した 16 進数値を含む Theme/ThemeTokens.swift と Color+App.swift を作成します
  5. コンポーネント — HTML の各セクションを SwiftUI ビューに変換します(ファイルごとに作業します)
  6. ナビゲーション — TabView(タブバー)または NavigationStack(スタック)を接続します
  7. ビルドと実行 — Xcode で Cmd+R を押します。ライトモードとダークモードの両方でテストします(Simulator で ⌃⌘A を押すと外観を切り替えられます)

トラブルシューティング

問題対処方法
ビューが画面からはみ出す.frame(maxWidth: .infinity) と親の ScrollView を追加します
テキストが予期せず省略される.lineLimit(nil) または .fixedSize(horizontal: false, vertical: true) を追加します
ダークモードで色が正しく表示されないAssets.xcassets の Color Set に Dark appearance が設定されていることを確認します
画像が読み込まれないAsyncImage の場合は URL が有効か確認します。ローカル画像の場合は、ファイルが Assets.xcassets に含まれている必要があります
TabView の項目にラベルが表示されないコンテンツはラップするビューを挟まず、.tabItem { } の直下に置く必要があります
シートを閉じられない@Environment(\.dismiss) var dismiss を追加し、シート内で dismiss() を呼び出します
プレビューがクラッシュする#Preview に有効なモックデータが設定されていることを確認します。フォールバックなしでオプショナルをアンラップしないでください

参考資料

  • resources/component-template.swift — SwiftUI ビューのボイラープレート
  • resources/layout-mapping.md — HTML/CSS → SwiftUI の完全なリファレンス
  • resources/architecture-checklist.md — リリース前チェックリスト
  • scripts/fetch-stitch.sh — 信頼性の高い GCS HTML ダウンローダー

コメント