iOS プロジェクト構成
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 Rustgen/apple/ ディレクトリは project.yml から生成される。cargo tauri ios init や cargo tauri ios dev/build を実行するたびに XcodeGen が project.yml を読み直し .xcodeproj を生成する。.xcodeproj を直接編集した内容は上書きされるリスクがある。
Tip
gen/apple/ を git に入れるのは、CI で ios init を再実行せずにビルドを再現したい場合などに限定するのが良い。通常の開発では gen/ を gitignore し、CLI に再生成させる。下に .gitignore の例を載せる。
lib.rs は run をエクスポートする必要がある
モバイル用エントリポイントは tauri_lib::run() を呼ぶ。src/ はおおよそ次のようになる:
#[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"
}
}
}| フィールド | 役割 |
|---|---|
developmentTeam | 10 文字の Apple Team ID。Xcode > Settings > Accounts > 対象チームの "Team ID" で確認 |
minimumSystemVersion | IPHONEOS_DEPLOYMENT_TARGET にマップ。デフォルト 13.0。モダンな Web API が必要なら 14.0+ へ |
frameworks | 追加でリンクする Apple フレームワーク。変更したら cargo tauri ios init の再実行が必要 |
bundleVersion | CFBundleVersion にマップ。デフォルトはトップレベルの 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 は CFBundleShortVersionString、CFBundleVersion など基本キーを含んだ Info.plist を自前で生成する。カメラ使用許可、ATS 例外、URL スキームハンドラなど追加のキーが必要なら、tauri.conf.json の隣に 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/ で管理される。このファイルは再生成されるため、編集は注意:
<?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.0 | iPhone 6s 以降 | WKWebView 機能が改善、container クエリが使える |
15.0 | iPhone 6s 以降 | 2025 年以降のモダン Web アプリには無難な選択 |
16.0 | iPhone 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
これは型システムやコンパイラが捕まえてくれるものではない。両方の分岐がコンパイルされ、シミュレータでは両方が到達可能で、誤っているのは実行時のパスだけである。サンドボックスのパス解決は必ず実機でテストするか、少なくとも解決されたパスがリポジトリではなくアプリコンテナの下にあることを確認すること。