zudo-tauri-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

iOS プロジェクト構成

作成 2026年4月16日更新 2026年5月28日Takeshi Takatsudo

cargo tauri ios init が生成するもの、gen/apple ディレクトリ、Info.ios.plist、bundle.iOS 設定

このページでは、Tauri プロジェクトで iOS を有効にしたときに追加されるファイル、iOS 固有ファイルが置かれる場所、そして tauri.conf.json のどのフィールドが実際に Xcode の挙動を左右するかを扱う。

cargo tauri ios init の実行

tauri.conf.json のあるディレクトリ(通常は src-tauri/)で実行する:

cargo tauri ios init

内部では cargo-mobile2 が動き、Xcode プロジェクトを scaffolding する。初回は依存のコンパイル、CocoaPods の配線、Xcode プロジェクト生成で数分かかる。

生成されるもの

ios init 後、プロジェクトに gen/ ディレクトリが追加される:

src-tauri/
  Cargo.toml
  tauri.conf.json
  Info.ios.plist           # (you create this yourself when needed)
  src/
    lib.rs                 # must export `run` for mobile entry point
  gen/
    apple/                 # iOS-specific output
      Podfile
      project.yml          # XcodeGen project definition
      <AppName>.xcodeproj/ # generated Xcode project
      <AppName>_iOS/
        <AppName>_iOS.entitlements  # capability entitlements
      Assets.xcassets/
      LaunchScreen.storyboard
      Sources/
        main.mm            # Objective-C++ entry point bridging to Rust

gen/apple/ ディレクトリは project.yml から生成されるcargo tauri ios initcargo tauri ios dev/build を実行するたびに XcodeGen が project.yml を読み直し .xcodeproj を生成する。.xcodeproj を直接編集した内容は上書きされるリスクがある。

Tip

gen/apple/ を git に入れるのは、CI で ios init を再実行せずにビルドを再現したい場合などに限定するのが良い。通常の開発では gen/ を gitignore し、CLI に再生成させる。下に .gitignore の例を載せる。

lib.rsrun をエクスポートする必要がある

モバイル用エントリポイントは tauri_lib::run() を呼ぶ。src/lib.rs はおおよそ次のようになる:

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|_app| Ok(()))
        .invoke_handler(tauri::generate_handler![])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

main.rs はライブラリを呼ぶだけでよい:

fn main() {
    app_lib::run()
}

app_lib は実際のクレートの lib 名に置き換える(Cargo.toml[lib] で設定する)。lib.rs がまだない場合は追加し、Cargo.toml を更新する:

[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

staticlib が iOS ビルド時に実際にリンクされるクレートタイプである。これがないと cargo tauri ios build は失敗する。

tauri.conf.json > bundle.iOS

iOS 固有のバンドル設定は bundle.iOS の下に書く:

{
  "bundle": {
    "active": true,
    "targets": "all",
    "iOS": {
      "developmentTeam": "ABCD123456",
      "minimumSystemVersion": "14.0",
      "frameworks": ["CoreHaptics", "WebKit"],
      "bundleVersion": "1"
    }
  }
}
フィールド役割
developmentTeam10 文字の Apple Team ID。Xcode > Settings > Accounts > 対象チームの "Team ID" で確認
minimumSystemVersionIPHONEOS_DEPLOYMENT_TARGET にマップ。デフォルト 13.0。モダンな Web API が必要なら 14.0+ へ
frameworks追加でリンクする Apple フレームワーク。変更したら cargo tauri ios init の再実行が必要
bundleVersionCFBundleVersion にマップ。デフォルトはトップレベルの version。ビルド番号を別管理するとき上書き

Note

developmentTeam は環境変数 APPLE_DEVELOPMENT_TEAM でも設定できる。Team ID をコミットしたくない CI で便利。env var が設定フィールドより優先される。

identifier のルール

tauri.conf.json のトップレベルの identifier が iOS のバンドル ID になる:

{
  "identifier": "com.takazudo.myapp"
}

Apple Developer ポータルで登録した App ID(または Personal Team 用に Xcode が自動生成したもの)と一致している必要がある。慣習として逆 DNS 形式(com.<org>.<app>)を使う。

Warning

英数字とドットだけにしておく。古い Tauri バージョンでは -_ を含むバンドル ID にバグがあった。com.takazudo.my-app で壊れた実例があり、com.takazudo.myapp の方が安全である。

Info.ios.plist: 追加キーのマージ

Tauri は CFBundleShortVersionStringCFBundleVersion など基本キーを含んだ Info.plist を自前で生成する。カメラ使用許可、ATS 例外、URL スキームハンドラなど追加のキーが必要なら、tauri.conf.json の隣に Info.ios.plist を作る:

src-tauri/Info.ios.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSCameraUsageDescription</key>
    <string>This app uses the camera to scan QR codes.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>This app needs access to your photos to attach images.</string>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
</dict>
</plist>

ここで定義したキーは生成済みの Info.plist にマージされる。デスクトップと iOS 共通のキーは通常の Info.plist に置いても良い -- Tauri は両方を読む。

Entitlements ファイル

Push 通知、App Groups、Associated Domains など entitlement が必要な capability は gen/apple/<AppName>_iOS/<AppName>_iOS.entitlements で管理される。このファイルは再生成されるため、編集は注意:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>aps-environment</key>
    <string>development</string>
</dict>
</plist>

実際には、無料の Personal Team ではこれらの capability を付与できないので、有料 Apple Developer Program に加入するまでこのファイルを触る機会はほぼない。

.gitignore の追加

iOS を有効にした Tauri プロジェクトでは既存の .gitignore を拡張する:

/target/
/gen/apple/Pods/
/gen/apple/Externals/
/gen/apple/build/

gen/apple/ の残りを無視するかどうかは判断が分かれる。Xcode プロジェクトをコミットしておけば GUI でビルド設定を調整しやすくなるが、その代わり "project.yml を編集 > 再 init" のワークフローを捨てることになる。安全なデフォルトは gen/ ごと無視して CI で再生成することである。

iOS バージョンマトリクス

minimumSystemVersion対応デバイスメモ
13.0(Tauri デフォルト)iPhone 6s 以降最小ライン。一部のモダン Web API が欠ける可能性あり
14.0iPhone 6s 以降WKWebView 機能が改善、container クエリが使える
15.0iPhone 6s 以降2025 年以降のモダン Web アプリには無難な選択
16.0iPhone 8 以降古い機種を切る代わりに Developer Mode の統一フローが手に入る

フロントエンドが実際に使う Web API をサポートする最低バージョンを選ぶ。詳細は caniuse.com を Safari-iOS でフィルタして確認すると良い。

iOS サンドボックスのパス解決

iOS ではアプリは好きな場所に書き込めない。すべてはアプリのサンドボックスコンテナの内側に置く必要がある。アプリの作業ファイルを置く標準的な場所はコンテナの Documents ディレクトリで、iOS では dirs::document_dir() がそこを指す(サンドボックスには Library/tmp/ も存在するが、作業ファイルには Documents が適切である)。アプリの作業ファイルはすべてここに振り向ける:

let workspace = dirs::document_dir()
    .expect("could not determine iOS documents directory")
    .join("my-app");
std::fs::create_dir_all(&workspace).ok();

得られるパスはサンドボックスの_内側_にある。サンドボックスの外側(リポジトリのパスや、デスクトップ向けに計算した ~/Documents のパスなど)にアンカーしようとすると、実機では書き込めず、しかも無言で失敗する。

cfg の順序の罠

ひとつのバイナリでデスクトップと iOS の両方を扱う場合、通常はプラットフォームと、デバッグ/リリースの両方で分岐する。その分岐の順序が重要で、間違えると半日を溶かす。

fn resolve_project_root() -> String {
    // iOS (debug OR release): always use the app sandbox documents directory.
    // Must be checked BEFORE the debug_assertions branch. On a simulator build
    // both `cfg!(target_os = "ios")` and `cfg!(debug_assertions)` are true, and
    // the repo-root path from CARGO_MANIFEST_DIR is outside the iOS container.
    #[cfg(target_os = "ios")]
    {
        let workspace = dirs::document_dir()
            .expect("could not determine iOS documents directory")
            .join("my-app");
        std::fs::create_dir_all(&workspace).ok();
        return workspace.to_string_lossy().to_string();
    }

    // Dev mode (non-iOS): the repo root is the parent of src-tauri/
    if cfg!(debug_assertions) {
        return std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .unwrap()
            .to_string_lossy()
            .to_string();
    }

    // Production (macOS and other non-iOS targets)
    #[cfg(not(target_os = "ios"))]
    {
        let home = dirs::home_dir().expect("could not determine home directory");
        home.join("Documents/my-app").to_string_lossy().to_string()
    }
}

肝心な点: iOS シミュレータでは、デバッグビルドにおいて cfg!(target_os = "ios")cfg!(debug_assertions)両方true に評価される。debug_assertions を先にチェックすると、シミュレータは dev 分岐に入り、CARGO_MANIFEST_DIR からリポジトリルート — iOS アプリコンテナの_外側_のパス — を返してしまう。するとアプリはファイルの読み書きに失敗するが、原因を指し示すエラーは出ない。シミュレータは Mac のファイルシステムを読めてしまうため、中途半端に動いているように見えることすらあり、これが追跡をいっそう厄介にする。

修正は純粋に順序の問題である。早期 return を持つ #[cfg(target_os = "ios")] ブロックが、debug_assertions のチェックよりもに来なければならない。同じパターンは resolve_app_config_dir() をはじめ、デバッグモードで分岐するあらゆるパスリゾルバに当てはまる — iOS の早期 return を先頭に置くこと。

Warning

これは型システムやコンパイラが捕まえてくれるものではない。両方の分岐がコンパイルされ、シミュレータでは両方が到達可能で、誤っているのは実行時のパスだけである。サンドボックスのパス解決は必ず実機でテストするか、少なくとも解決されたパスがリポジトリではなくアプリコンテナの下にあることを確認すること。

公式ドキュメント

Revision History

Takeshi Takatsudo作成: 2026-04-17T08:50:05+09:00更新: 2026-05-29T05:48:31+09:00