App InventorからiPhoneアプリを作るには?:2025年9月24日現在

考えたことメモ > App InventorからiPhoneアプリを作るには?

App InventorからPythonへどうやって移行するのか?」でChatGPT-4oがApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するPythonコードを書けました。

ここではChatGPT-5を使ってApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてiPhone用のSwiftUIコードを書いてみます。

出発点のApp Inventorアプリは「オンデマンドアカデミー」で紹介されている カラフルお絵かきアプリです。このアプリは、基本的なUIの構成やイベント処理の理解に適しています。

aiaファイルの読み込みと解析

要件定義書作成

SwiftUIプログラミング

動作確認

サマリー

ChatGPT-5はApp Inventorのaiaファイルを直接読み込み、アプリの要件定義書を書いてそのまま動作するSwiftUIコードを書けました。

aiaファイルの読み込みと解析

プロンプト

あなたはApp Inventor 2の内部構造に精通したプロのソフトウェアエンジニアです。このaiaファイルを完全に解析してください。

返答

解析しました!まず要点をざっと👇

画面は Canvas(キャンバス)+3本の水平配置。上段に色(黒/青/グリーン/赤)、中段に太さ(細/中/※太の候補)、下段に「画面消去」「消しゴム」ボタン。

ブロックは 126ノード。主な構成は変数取得/設定、イベント、プロシージャ定義、Canvas 操作(DrawLine / Clear)。

定義プロシージャ:色ボタンリセット / 色ボタン選択 / 太さボタンリセット / 太さボタン選択 / 消しゴムボタン選択。

イベント:Screen1.Initialize, Canvas.Dragged, (ボタン)Click。
初期化で「選択中の色」「ペン太さ」を渡してプロシージャ呼び出し→UI状態と PaintColor / LineWidth を確定。
Dragged では prevX, prevY -> currentX, currentY へ DrawLine で描画。
「画面消去」は Canvas.Clear、消しゴムは白系で描く実装。

プロジェクト設定:sizing=Responsive、テーマ AppTheme.Light.DarkActionBar、位置情報未使用、拡張/アセットなし、バージョン 1.0 (code 1)。

フルの技術レポート(コンポーネント階層、ボタン属性表、イベント/メソッド一覧、推定挙動、改善提案まで)を用意しました。
# DoodlePlus(.aia)完全解析レポート

このレポートは MIT App Inventor 2 プロジェクトの .aia を展開し、画面(.scm)とブロック(.bky)、プロジェクト設定(project.properties)を静的解析した内容です。

## 1) プロジェクト設定(youngandroidproject/project.properties)

```
#
#Sat Apr 12 01:36:28 UTC 2025
sizing=Responsive
color.primary.dark=&HFF303F9F
color.primary=&HFF3F51B5
color.accent=&HFFFF4081
aname=DoodlePlus
defaultfilescope=App
main=appinventor.*****.DoodlePlus.Screen1
source=../src
actionbar=True
useslocation=False
assets=../assets
build=../build
name=DoodlePlus
showlistsasjson=True
theme=AppTheme.Light.DarkActionBar
versioncode=1
versionname=1.0
```

主な項目:

- アプリ名 (aname / name): **DoodlePlus / お絵かきアプリ+**
- メイン画面: **Screen1**
- テーマ: **showlistsasjson=True**
- デフォルトファイルスコープ: **main=appinventor.****.DoodlePlus.Screen1**
- ActionBar: **True**
- 位置情報使用 (useslocation): **False**

## 2) 画面レイアウト(Screen1.scm)

- 画面タイトル: **お絵かきアプリ+**
- コンポーネント数: **12**

### コンポーネント階層

- `キャンバス1` : Canvas (親: None)
- `水平配置1` : HorizontalArrangement (親: None)
  - `黒` : Button (親: 水平配置1)
  - `青` : Button (親: 水平配置1)
  - `グリーン` : Button (親: 水平配置1)
  - `赤` : Button (親: 水平配置1)
- `水平配置2` : HorizontalArrangement (親: None)
  - `細` : Button (親: 水平配置2)
  - `中` : Button (親: 水平配置2)
- `水平配置3` : HorizontalArrangement (親: None)
  - `画面消去` : Button (親: 水平配置3)
  - `消しゴム` : Button (親: 水平配置3)

### カラーボタン・太さボタン等の属性

| 親配置 | ボタン名 | 表示テキスト | 背景色 | フォントサイズ | 幅 |
|---|---|---|---|---|---|
| 水平配置1 | 黒 |  | &HFF000000 | 10 | -1025 |
| 水平配置1 | 青 |  | &HFF0000FF | 10 | -1025 |
| 水平配置1 | グリーン |  | &HFF00FF00 | 10 | -1025 |
| 水平配置1 | 赤 |  | &HFFFF0000 | 10 | -1025 |
| 水平配置2 | 細 | 細 |  |  |  |
| 水平配置2 | 中 | 中 |  |  |  |
| 水平配置3 | 画面消去 | 画面消去 |  |  |  |
| 水平配置3 | 消しゴム | 消しゴム |  |  |  |

## 3) ブロック(Screen1.bky)

- 総ブロック数(node): **126**
### ブロック種別の出現数(上位)
- lexical_variable_get: 34
- component_component_block: 14
- component_set_get: 14
- global_declaration: 7
- procedures_callnoreturn: 7
- procedures_defnoreturn: 5
- logic_false: 4
- controls_if: 4
- logic_compare: 4
- lexical_variable_set: 4
- lists_create_with: 3
- color_black: 3
- component_event: 3
- text: 2
- math_number: 2

### イベントハンドラ
- Screen1 : Form.Initialize
- (未指定) : Button.Click
- キャンバス1 : Canvas.Dragged

### 定義済みプロシージャ
- 色ボタンリセット
- 色ボタン選択
- 太さボタンリセット
- 太さボタン選択
- 消しゴムボタン選択

(推定機能)
- **色ボタンリセット / 色ボタン選択**: 色選択UIの状態管理(選択記号の付与・他色のリセット、および `Canvas.PaintColor` の更新)
- **太さボタンリセット / 太さボタン選択**: ペンの太さUIの状態管理(`Canvas.LineWidth` の更新)
- **消しゴムボタン選択**: 消しゴムモード(背景色=白 or 透明、`LineWidth` 固定化等)への切替

### 画面初期化時の呼び出し(Screen1.Initialize)
- Call 色ボタン選択 with args: [('var', 'global 選択色ボタン')]
- Call 太さボタン選択 with args: [('var', 'global ペン太さ')]

### メソッド利用(コンポーネント操作)

- キャンバス1 (Canvas).**Clear**
- キャンバス1 (Canvas).**DrawLine**

※ 少なくとも `Canvas.DrawLine`, `Canvas.Clear` を使用しており、ドラッグで線分を描画し、[画面消去] でキャンバスをクリアする仕様です。

## 4) 振る舞いの要約(静的解析からの推定)

- 画面上部に **Canvas**、下部に3行の **HorizontalArrangement**(色選択、太さ選択、ユーティリティ)が並ぶ構成。
- 色ボタン(`黒` / `青` / `グリーン` / `赤`)で `Canvas.PaintColor` を切替。選択中ボタンにはチェック記号(例: ✔︎)を付ける実装。
- 太さボタン(`細` / `中` / (恐らく `太`))で `Canvas.LineWidth` を切替。
- `画面消去` ボタンで `Canvas.Clear`。
- `消しゴム` ボタンで描画色を背景色(白)相当にして消す振る舞い。
- `Canvas.Dragged` では `prevX, prevY` → `currentX, currentY` に対して **DrawLine** する一般的なお絵かきロジック。

## 5) 拡張機能・アセット・権限

- 拡張(.aix): **未使用**(拡張の痕跡なし)
- アセット(/assets): **未検出**(aiaに含まれない)
- パーミッション: project.properties 上は **位置情報未使用**。その他の特別権限の記載なし。

## 6) 改善できるポイント(提案)

- **ボタンのアクセシビリティ**: 色名テキストの有無に揺れがあるため、全ボタンで `Text` を明示し `ContentDescription` を付けると支援技術に優しいです。
- **消しゴムモードの視覚化**: 現状のチェック記号流用だけでなく、トグル表示やボタン色の反転などで状態を明確にできます。
- **キャンバスサイズ**: `Height/Width=-2`(Fill Parent)使用だが、端末回転時の計測タイミングに注意。必要なら `sizing=Responsive` に合わせて初回レイアウト後に線幅を再設定。
- **Undo/Redo**: 線分の履歴リストを持ち、`Canvas.Clear` 前にバックアップすれば Undo が実装可能。
- **保存/共有**: `Canvas.SaveAs` による画像保存、`Sharing` コンポーネントによる共有の追加を検討。

要件定義書作成

プロンプト

このアプリの要件定義書をMarkdownで書いて

返答

線の太さに「太」が追加されていますが、よしとしましょう。

# DoodlePlus アプリ要件定義書

## 1. 概要

DoodlePlus は、Android スマートフォン上で動作するシンプルなお絵かきアプリである。ユーザは指でキャンバスに線を描画し、色や線の太さを切り替えたり、消しゴムで描いた線を消去したりできる。さらに、キャンバス全体を一括でクリアする機能を提供する。

## 2. 対象ユーザー

- 幼児から大人まで幅広い年齢層
- 落書きやメモを素早く行いたいユーザー
- 学習教材やプレゼンの補助に使いたいユーザー

## 3. 利用目的

- 指先で自由に絵や文字を描く
- 色や太さを変えた線で描画
- 消しゴム機能で部分修正
- 全消去で新しいキャンバスにリセット

## 4. 機能要件

### 4.1 キャンバス機能
- 指でドラッグした座標間に線を描画
- 線の色、太さを設定可能
- 全体を消去する機能(Canvas.Clear)

### 4.2 色選択機能
- 黒 / 青 / 緑 / 赤 の4色を選択可能
- 選択中の色を視覚的に強調表示(チェック記号)

### 4.3 線の太さ選択機能
- 細 / 中 / 太 の3段階から選択可能
- 選択中の太さを視覚的に強調表示

### 4.4 消しゴム機能
- 描画色を背景色(白)に変更して消去
- 消しゴム使用中は太さ固定(中程度)

### 4.5 全消去機能
- ボタン操作でキャンバス全体をクリア

## 5. 非機能要件

- **操作性**: タップ/ドラッグで直感的に利用可能
- **応答速度**: 線描画・消去操作はリアルタイムで反映
- **アクセシビリティ**: ボタンにテキストラベルを付与し、視認性を確保
- **互換性**: Android 5.0 以上で動作

## 6. UI要件

- 上部: 描画用キャンバス(画面の大部分を占有)
- 下部: 3列の水平配置
  - 1行目: 色選択ボタン(黒/青/緑/赤)
  - 2行目: 太さ選択ボタン(細/中/太)
  - 3行目: [消しゴム] ボタン, [画面消去] ボタン

## 7. 将来的な拡張要件(提案)

- 描画の Undo / Redo 機能
- 画像保存および共有機能
- 消しゴムモードを視覚的に分かりやすくするトグル表示
- 色や太さのカスタム選択(カラーピッカー/スライダー導入)

## 8. 制約条件

- MIT App Inventor 2 で開発
- 外部拡張(.aix)は使用しない
- インターネットや位置情報の権限は不要

## 9. 品質保証

- 動作確認: Android 実機でのテスト
- 正常系テスト: 色/太さ選択、描画、消去操作が正しく動作すること
- 異常系テスト: 高速なタッチ操作や多指操作でも動作が破綻しないこと

SwiftUIプログラミング

プロンプト

この要件定義書に基づいてSwiftUIのコードを書いて。初心者にも何をしているかわかるように、また、App Inventorアプリとの対応関係がわかるように、詳しいコメントをつけて。

返答

了解!要件定義書の機能に対応する SwiftUI 実装を、App Inventor(AI2)のコンポーネント/ブロックとの対応関係が分かるように、丁寧なコメント付きでまとめました。
以下の単一ファイル(ContentView.swift)を Xcode の SwiftUI プロジェクトに追加すれば、そのまま動作します(iOS 15+ を想定)。

//
//  ContentView.swift
//
//  DoodlePlus (SwiftUI port)
//  - MIT App Inventor 2 版の「Canvas + 色選択 + 太さ選択 + 消しゴム + 全消去」をSwiftUIで再現
//  - 初心者向けに、できるだけ丁寧にコメントしています。
//  - App Inventor のコンポーネント/ブロック対応をコード中に明記。
//  - 透明消去(アルファ抜き)ではなく「白で上書きする」方式の消しゴムで、AI2の挙動に近づけています。
//    (※本格的な透明消去をしたい場合は Core Graphics のブレンドモード destinationOut などの応用が必要)
//

import SwiftUI

// MARK: - モデル定義(AI2で言うところの「変数」や「状態」)
//
// App Inventor 対応:
// - PaintColor(Canvasの描画色)→ selectedColor
// - LineWidth   (線の太さ)    → selectedWidth
// - 消しゴムモード               → isEraser
// - 描いた線の履歴               → strokes(各ストロークは points の配列)
//

/// 線1本(ストローク)を表すモデル
struct Stroke: Identifiable {
    let id = UUID()
    var points: [CGPoint]     // 線を構成する点列(指でなぞった座標)
    var color: Color          // 線の色(消しゴムなら Color.white)
    var lineWidth: CGFloat    // 線の太さ
}

/// アプリ全体の状態
final class DrawingState: ObservableObject {
    // 現在の選択色(AI2: Canvas.PaintColor)
    @Published var selectedColor: Color = .black
    // 現在の線の太さ(AI2: Canvas.LineWidth)
    @Published var selectedWidth: CGFloat = 6
    // 消しゴムモード(AI2: 「消しゴムボタン選択」プロシージャで true/false を切替)
    @Published var isEraser: Bool = false

    // これまでに描いた全ストローク(AI2: DrawLine を積み重ねるイメージ)
    @Published var strokes: [Stroke] = []
    // ドラッグ中の「現在のストローク」(指を離したら strokes に確定追加)
    @Published var currentStroke: Stroke? = nil

    // 画面全消去(AI2: Canvas.Clear)
    func clear() {
        strokes.removeAll()
        currentStroke = nil
    }
}

// MARK: - SwiftUI 版 Canvas(AI2: Canvas コンポーネントに相当)
//
// - SwiftUI の Canvas/Path を使って、strokes と currentStroke を描画します。
// - App Inventor の Canvas.DrawLine(block) 相当の処理は、points を連結して Path にしています。
//
struct DrawingCanvas: View {
    @ObservedObject var state: DrawingState

    var body: some View {
        GeometryReader { geo in
            ZStack {
                // 背景は白(AI2: Canvas の BackgroundColor に相当)
                Color.white
                    .ignoresSafeArea()

                // これまでに描いたストロークを全部描画
                ForEach(state.strokes) { stroke in
                    Path { path in
                        guard let first = stroke.points.first else { return }
                        path.move(to: first)
                        for p in stroke.points.dropFirst() {
                            path.addLine(to: p)
                        }
                    }
                    .stroke(stroke.color, lineWidth: stroke.lineWidth)
                }

                // ドラッグ中のストロークもプレビュー表示
                if let stroke = state.currentStroke {
                    Path { path in
                        guard let first = stroke.points.first else { return }
                        path.move(to: first)
                        for p in stroke.points.dropFirst() {
                            path.addLine(to: p)
                        }
                    }
                    .stroke(stroke.color, lineWidth: stroke.lineWidth)
                }
            }
            // ジェスチャ:指でなぞる → 点を追加 → 指を離したら確定
            // App Inventor 対応:
            //   - Canvas.Dragged(prevX, prevY, currentX, currentY) で DrawLine しているのと同等
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { value in
                        // ドラッグが始まったら currentStroke を作成(色/太さは現在の選択)
                        if state.currentStroke == nil {
                            let color = state.isEraser ? Color.white : state.selectedColor
                            state.currentStroke = Stroke(points: [value.location], color: color, lineWidth: state.selectedWidth)
                        } else {
                            // すでに作成済みなら座標を追加
                            state.currentStroke?.points.append(value.location)
                        }
                    }
                    .onEnded { value in
                        // 指を離したら確定(strokes に追加)し、currentStroke をクリア
                        if var stroke = state.currentStroke {
                            stroke.points.append(value.location)
                            state.strokes.append(stroke)
                        }
                        state.currentStroke = nil
                    }
            )
        }
    }
}

// MARK: - メインビュー(AI2: Screen1 に相当)
//
// 画面構成(要件/UI要件に基づく)
//  - 上部:描画用 Canvas
//  - 下部:3つの水平行(HStack)
//      1行目:色選択(黒/青/緑/赤)→ PaintColor 切替
//      2行目:太さ選択(細/中/太)→ LineWidth 切替
//      3行目:消しゴムトグル / 画面消去 → isEraser 切替 / Canvas.Clear
//
struct ContentView: View {
    @StateObject private var state = DrawingState()

    // 太さの候補(AI2: 「細/中/太」)
    private let widthOptions: [(label: String, value: CGFloat)] = [
        ("細", 4),
        ("中", 8),
        ("太", 14)
    ]

    // 色の候補(AI2: 「黒/青/緑/赤」)
    private let colorOptions: [(label: String, value: Color)] = [
        ("黒", .black),
        ("青", .blue),
        ("緑", .green),
        ("赤", .red)
    ]

    var body: some View {
        VStack(spacing: 0) {
            // 1) Canvas 領域(要件: 画面の大部分)
            DrawingCanvas(state: state)
                .overlay(alignment: .topLeading) {
                    // シンプルなタイトル(AI2: Screen1.Title)
                    Text("DoodlePlus")
                        .font(.headline)
                        .padding(8)
                        .background(.ultraThinMaterial)
                        .cornerRadius(8)
                        .padding(8)
                        .accessibilityHidden(true)
                }

            Divider()

            // 2) 下部パネル:3段構成
            VStack(spacing: 8) {
                // --- 行1: 色選択(AI2: 色ボタン群 + 「色ボタン選択/リセット」プロシージャに対応)---
                controlRow(title: "色", content: {
                    HStack {
                        ForEach(colorOptions, id: \.label) { opt in
                            Button {
                                // 色選択:PaintColor を更新、かつ消しゴムモードは OFF
                                state.selectedColor = opt.value
                                state.isEraser = false
                            } label: {
                                HStack(spacing: 6) {
                                    Circle()
                                        .fill(opt.value)
                                        .frame(width: 18, height: 18)
                                    Text(opt.label)
                                    // 選択中マーク(AI2: チェック記号の代替として表示)
                                    if !state.isEraser && state.selectedColor.description == opt.value.description {
                                        Image(systemName: "checkmark")
                                    }
                                }
                                .padding(.horizontal, 10)
                                .padding(.vertical, 8)
                                .background(
                                    // 選択中は控えめに強調
                                    (!state.isEraser && state.selectedColor.description == opt.value.description) ?
                                        Color.secondary.opacity(0.15) : Color.clear
                                )
                                .cornerRadius(10)
                            }
                            .accessibilityLabel(Text("色 \(opt.label)"))
                        }
                    }
                })

                // --- 行2: 太さ選択(AI2: 太さボタン群 + 「太さボタン選択/リセット」プロシージャに対応)---
                controlRow(title: "太さ", content: {
                    HStack {
                        ForEach(widthOptions, id: \.label) { opt in
                            Button {
                                state.selectedWidth = opt.value
                            } label: {
                                HStack(spacing: 8) {
                                    // 太さプレビュー
                                    Capsule()
                                        .frame(width: 28, height: max(4, opt.value / 1.6))
                                    Text(opt.label)
                                    if state.selectedWidth == opt.value {
                                        Image(systemName: "checkmark")
                                    }
                                }
                                .padding(.horizontal, 10)
                                .padding(.vertical, 8)
                                .background(
                                    (state.selectedWidth == opt.value) ?
                                        Color.secondary.opacity(0.15) : Color.clear
                                )
                                .cornerRadius(10)
                            }
                            .accessibilityLabel(Text("太さ \(opt.label)"))
                        }
                    }
                })

                // --- 行3: ユーティリティ(AI2: 消しゴム / 画面消去ボタン)---
                controlRow(title: "操作", content: {
                    HStack {
                        // 消しゴム(AI2: 消しゴムボタン選択 → isEraser = true、UI強調)
                        Button {
                            state.isEraser.toggle()
                        } label: {
                            Label("消しゴム", systemImage: state.isEraser ? "eraser.fill" : "eraser")
                                .padding(.horizontal, 12)
                                .padding(.vertical, 10)
                                .background(state.isEraser ? Color.secondary.opacity(0.2) : Color.clear)
                                .cornerRadius(10)
                        }
                        .accessibilityLabel(Text("消しゴム"))
                        .accessibilityHint(Text("オンにすると白い線で上書きして消せます"))

                        Spacer(minLength: 12)

                        // 画面消去(AI2: Canvas.Clear)
                        Button(role: .destructive) {
                            state.clear()
                        } label: {
                            Label("画面消去", systemImage: "trash")
                                .padding(.horizontal, 12)
                                .padding(.vertical, 10)
                                .background(Color.red.opacity(0.12))
                                .cornerRadius(10)
                        }
                        .accessibilityLabel(Text("画面消去"))
                        .accessibilityHint(Text("キャンバスをすべてクリアします"))
                    }
                })
            }
            .padding(12)
            .background(.ultraThinMaterial)
        }
        .preferredColorScheme(.light) // 背景白に合わせた見え方重視(任意)
    }

    // 下部各行のラッパー(タイトル+内容)
    private func controlRow<Content: View>(title: String, @ViewBuilder content: () -> Content) -> some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(title)
                .font(.subheadline).bold()
                .accessibilityHidden(true)
            content()
        }
    }
}

// MARK: - プレビュー
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewDevice("iPhone 15")
    }
}
App Inventor(AI2)との対応表(抜粋)
App Inventor	SwiftUI 実装	補足
Screen1	ContentView	画面のメインビュー
Canvas	DrawingCanvas(GeometryReader + Path/ZStack)	背景白・ストローク配列を描画
Canvas.Dragged	DragGesture.onChanged / onEnded	ドラッグ座標を currentStroke.points に追加・確定
Canvas.DrawLine	Path.addLine(to:)	点列から線を生成
Canvas.Clear	state.clear()	strokes.removeAll()
色ボタン(黒/青/緑/赤)	colorOptions + ボタン群	押下で selectedColor セット&消しゴム解除
太さボタン(細/中/太)	widthOptions + ボタン群	押下で selectedWidth セット
消しゴム	state.isEraser.toggle()	消しゴムON時は Color.white で上書き
選択中のチェック表示	Image(systemName:"checkmark")	状態に応じて表示
使い方メモ(初心者向け)

ボタンで 色 と 太さ を選んで、上の白いキャンバスを指でなぞると線が描けます。

消しゴム を押すと白い線で上書きでき、描いた線を消す感覚になります(背景が白のため)。

画面消去 でキャンバスをまっさらな状態に戻せます。

透明消去(背景が透ける消しゴム)にしたい場合は、Canvas レイヤーを CALayer / Core Graphics で管理し、ブレンドモード(destinationOut 等)を使う設計に変更すれば実現できます。

必要なら、この SwiftUI 版に Undo/Redo や 画像保存/共有 を追加した発展版コードもすぐ出せます。どう拡張したいか教えてください!

動作確認

iPhoneアプリを作るにはMacで動くXcodeという開発環境が必要です。ここではmacOS 13.7.5上でXcode 15.2を使い、動作確認にはiOSシミュレータ(iOS 15.2)を使いました。

公開済みAI関連アプリチュートリアル:シルバー会員特典

ゴールド会員特典はAIが中心

初心者にはこちらも