Android Studio で Swift / iOS アプリを開発するノート

Xcode を直接立ち上げず、Android Studio(IntelliJ ベース)+ CLI で iPhone / iPad アプリを作る運用のまとめ。シミュレータのタップ自動化や Lint など周辺ツールも含む。

目次
  1. 全体構成
  2. 初回セットアップ
    1. 必要ツール
    2. Android Studio 側
  3. ビルド & 実行
  4. シミュレータ操作の自動化
    1. タップ自動化(idb 経由)
    2. スクリーンショット
  5. Lint / フォーマッタ
    1. SwiftLint
    2. SwiftFormat(任意)
  6. Git pre-commit hook の運用
  7. 典型ワークフロー
  8. ハマりどころメモ
  9. 補足: なぜ Android Studio?

1. 全体構成

┌─ Android Studio (エディタ) ─────────┐
│  ├─ Swift コードの編集             │
│  ├─ Run/Debug Configurations から   │
│  │   外部スクリプト経由でビルド・起動 │
│  └─ ターミナル(make / scripts)    │
└──────────────────────────────────┘
         │
         ▼
┌─ CLI ツール群 ──────────────────────┐
│  xcodebuild  → iOS Simulator ビルド │
│  simctl      → Simulator 起動・操作 │
│  idb         → タップ自動化         │
└──────────────────────────────────┘
         │
         ▼
┌─ iOS Simulator ──────────────────┐
│  アプリの起動・操作・SS 撮影が可能  │
└────────────────────────────────┘

ポイント: Android Studio 自体は Swift をネイティブビルドできない。Xcode CLI(xcodebuild)に投げる Run Configuration を作るのが肝。

2. 初回セットアップ

2.1. 必要ツール

# Xcode CLI
xcode-select --install
xcodebuild -version

# Homebrew でツール群(jq は idb の出力パースに使う)
brew install jq

# fb-idb は Python 3.14 で動かないので 3.11 系に固定して pipx で入れる(後述)
brew install python@3.11 pipx
pipx install --python python3.11 fb-idb

2.2. Android Studio 側

Run Configuration(外部スクリプト実行)

Run/Debug Configurations → +Shell Script を選択し、Script path にビルド/実行スクリプトのパスを指定する。

構成名(例)中身
Run (iPhone)xcodebuild -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build + Simulator 起動
Run (iPad)iPad 向け destination でビルド
Build onlyビルドのみ
Screenshotxcrun simctl io booted screenshot

Swift プラグイン: AppCode は撤退済み。IntelliJ の Swift サポートは限定的なので、コード補完は割り切って cmd-click での定義ジャンプと grep / Find in Files に頼る。本格的に補完が必要なときだけ Xcode を別途開く(open *.xcodeproj)。

3. ビルド & 実行

# 基本形
xcodebuild \
  -project YourApp.xcodeproj \
  -scheme YourApp \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
  -quiet build

ログを抑制したいので -quiet を付けるのがコツ。エラーだけ出る。

Makefile でラップしておくと便利:

PROJECT     := YourApp.xcodeproj
SCHEME      := YourApp
DEVICE_NAME ?= iPhone 17 Pro
DEST        := platform=iOS Simulator,name=$(DEVICE_NAME)

build:
	xcodebuild -project $(PROJECT) -scheme $(SCHEME) \
		-destination '$(DEST)' -quiet build

run: build
	xcrun simctl boot '$(DEVICE_NAME)' 2>/dev/null || true
	open -a Simulator
	xcrun simctl install booted $(BUILT_APP_PATH)
	xcrun simctl launch booted $(BUNDLE_ID)

test:
	xcodebuild -project $(PROJECT) -scheme $(SCHEME) \
		-destination '$(DEST)' -quiet test

clean:
	xcodebuild -project $(PROJECT) -scheme $(SCHEME) clean -quiet

install-hooks:
	./scripts/install_hooks.sh

$(BUILT_APP_PATH)$(BUNDLE_ID)xcodebuild -showBuildSettings から取得するか、別シェルスクリプトに切り出すと安定する。

4. シミュレータ操作の自動化

4.1. タップ自動化(idb 経由)

idb_companion をバックグラウンド起動 → idb connect で接続済みなら、シミュレータがフォーカスにあるかどうかに関わらず操作できる。

準備(セッションごと)

idb_companion は永続化されないため、Mac を再起動するか companion が落ちたら再度起動が必要。~/.zshrc から自動起動するスクリプトを噛ませると楽(§8 参照)。

# 起動中の Simulator UDID を取得
UDID=$(xcrun simctl list devices booted -j \
  | jq -r '.devices | to_entries[] | .value[] | select(.state=="Booted") | .udid' \
  | head -1)

# companion をバックグラウンド起動 → 起動ログから grpc_port を取得
idb_companion --udid "$UDID" > /tmp/idb_companion.log 2>&1 &
sleep 1
PORT=$(jq -r '.grpc_swift_port // .grpc_port' /tmp/idb_companion.log | head -1)

# クライアント側で接続
idb connect localhost "$PORT"

使い方

# 座標タップ(points)
idb ui tap 200 400

# AXLabel から要素の frame を取得(中央座標を計算して tap に渡す)
idb ui describe-all \
  | jq -r '.[] | select(.AXLabel=="開始") | "\(.frame.x + .frame.width/2) \(.frame.y + .frame.height/2)"' \
  | xargs idb ui tap

ハマりどころ

4.2. スクリーンショット

DIR=tmp/ss/$(date +%Y%m%d)
mkdir -p "$DIR"
xcrun simctl io booted screenshot "$DIR/ss_$(date +%H%M%S).png"

時分秒入りファイル名でディレクトリ分けして保存しておくと、後で見直しやすい。mkdir -p を忘れると simctl io ... screenshot はサイレントに失敗するので注意。

5. Lint / フォーマッタ

5.1. SwiftLint

brew install swiftlint
swiftlint                          # プロジェクトルートで実行
swiftlint --fix                    # 自動修正可能なものを修正
swiftlint --path Sources/          # 特定パス

設定ファイル .swiftlint.yml の例

identifier_name:
  min_length:
    warning: 1    # 1文字変数を許容(i, q, c 等のループ変数)
  max_length:
    warning: 60
  excluded:
    - id

line_length:
  warning: 200
  error: 300
  ignores_comments: true
  ignores_urls: true
  ignores_interpolated_strings: true

function_body_length:
  warning: 100
  error: 150

file_length:
  warning: 700
  error: 1200

disabled_rules:
  - todo    # TODO コメントは開発中に有用

excluded:
  - SomeTests
  - SomeUITests

5.2. SwiftFormat(任意)

.swiftformat を導入すれば自動整形できるが、SwiftLint の --fix でも大半は足りる。チームで強制ルールが要るとき以外は SwiftLint だけで十分。

6. Git pre-commit hook の運用

ステージされた特定ファイルだけを検証する pre-commit パターン。

scripts/hooks/pre-commit の例

#!/bin/bash
set -e

ROOT="$(git rev-parse --show-toplevel)"

# 対象ファイルの抽出(例: Swift ファイルのみ)
STAGED=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.swift$' || true)
[ -z "$STAGED" ] && exit 0

# SwiftLint があれば実行
if command -v swiftlint > /dev/null; then
    echo "[pre-commit] Running SwiftLint on staged files..."
    while IFS= read -r f; do
        swiftlint lint --quiet --path "$ROOT/$f" || exit 1
    done <<< "$STAGED"
fi

インストーラ scripts/install_hooks.sh

#!/bin/bash
set -e
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
HOOKS="$ROOT/.git/hooks"
SOURCE="$ROOT/scripts/hooks/pre-commit"

# 既存があればバックアップ
if [ -f "$HOOKS/pre-commit" ] && [ ! -L "$HOOKS/pre-commit" ]; then
    mv "$HOOKS/pre-commit" "$HOOKS/pre-commit.backup.$(date +%Y%m%d_%H%M%S)"
fi

# シンボリックリンクで設置(リポジトリ更新に追従)
ln -sf "../../scripts/hooks/pre-commit" "$HOOKS/pre-commit"
chmod +x "$SOURCE"
echo "Installed pre-commit hook"

make install-hooks で叩けるようにしておくと、新規 clone 後すぐ導入できる。

回避(非推奨): git commit --no-verify

コツ

7. 典型ワークフロー

# 1. ブランチ作成・コード編集(Android Studio)

# 2. ローカル lint
swiftlint --fix
swiftlint                # warning/error が 0 か確認

# 3. ビルド
make build               # まずビルドが通るか確認
make run                 # Simulator で起動

# 4. UI 動作確認(手動 or idb)
idb ui tap 200 400
xcrun simctl io booted screenshot tmp/ss/$(date +%Y%m%d)/...

# 5. テスト
make test

# 6. コミット(pre-commit が走る)
git add <files>
git commit -m "..."

8. ハマりどころメモ

9. 補足: なぜ Android Studio?