Configuration

iOS

The desktop application of a Récit site also builds for iOS: the same app: section, the same shell, an .ipa at the end. It needs a Mac.

Written from Tauri's documentation

This flow (plan 18, lot APP8) was written on a Linux machine from the documentation of Tauri 2 and has not run on a Mac yet. The commands, the signing steps and the workflow are the ones Tauri documents; the first run on a Mac confirms or corrects them, and this note goes away then. The section Not verified yet lists what that run must look at.

Prerequisites

On a Mac:

  • Xcode, complete (from the App Store), then xcode-select --install;
  • CocoaPods (brew install cocoapods), which Tauri's prerequisites still name;
  • Rust stable with the iOS targets: rustup target add aarch64-apple-ios aarch64-apple-ios-sim (add x86_64-apple-ios for the simulator of an Intel Mac);
  • an Apple Developer team, to sign anything that leaves the simulator;
  • @recit/app installed in the site, as for the desktop.

Elsewhere, recit app ios … stops with these prerequisites and runs nothing.

Section

KeyDefaultMeaning
ios.developmentTeamnone, or APPLE_DEVELOPMENT_TEAMthe team that signs the application
identifiersee the app pagebecomes the bundle identifier, final
iconmetadata.faviconthe AppIcon set is derived from it
versionthe site's package.jsonthe version Xcode shows; the build number is a flag

The generated project ships a default launch screen; the application keeps it. The team goes into the Xcode project when it is generated (recit app ios init) and into the signature of every build: set it before the first init, or regenerate the project after.

Commands

CommandEffect
recit app ios initgenerates the Xcode project once, in .recit/app/gen/apple/ (ignored by git); -- --ci skips the prompts
recit app ios devruns the application in the simulator, or on a device, against recit-site dev, with hot reload
recit app ios build -- --export-method debugginga signed .ipa for the devices of the team, in .recit/app/gen/apple/build/
recit app ios build -- --export-method app-store-connecta signed .ipa for TestFlight and the App Store; release-testing is the ad hoc export for registered devices
recit app build --target iosthe site build followed by tauri ios build with Tauri's default export: pass --export-method to choose one

The arguments after -- go to tauri ios … as they are, --build-number <n> included. The export methods are the names of Xcode 15; @recit/app also accepts the old ones (app-store, ad-hoc, development) and translates them. When a tool is missing, the command says which one, and how to install it.

Signing

  • A team. ios.developmentTeam in the app: section, or the APPLE_DEVELOPMENT_TEAM variable that Tauri reads itself; the config wins. Without a team, only the simulator builds.
  • A certificate and a profile. An Apple Development or Distribution certificate in the keychain and a provisioning profile that names the bundle identifier. On a Mac, Xcode manages both with automatic signing; in CI they come from secrets (below).
  • An App Store Connect API key (issuer, key id, .p8 file) for the app-store-connect export and the TestFlight upload: APPLE_API_ISSUER, APPLE_API_KEY, APPLE_API_KEY_PATH.

The identifier is final once the application exists in App Store Connect.

In CI

The workflow .github/workflows/app-ios.yaml runs by hand on a macos-15 runner, like the desktop one:

TerminalBash
gh workflow run app-ios.yaml -f site=examples/site-embed -f export_method=debugging

It installs Rust with the iOS targets, builds the site, generates the Xcode project, then:

  • without the signing secrets, compiles the whole application for the simulator, unsigned (xcodebuild build … CODE_SIGNING_ALLOWED=NO): the project and the configuration are proven, no .ipa leaves the runner;
  • with them, imports the certificate and the profile into a temporary keychain, runs recit app ios build -- --export-method <input> --build-number <run number> and uploads the .ipa as an artifact; with the input upload and the app-store-connect export, it also sends the .ipa to TestFlight with xcrun altool --upload-app.
SecretContent
APPLE_DEVELOPMENT_TEAMthe team id (ten characters)
IOS_CERTIFICATEthe signing certificate, a .p12 in base64
IOS_CERTIFICATE_PASSWORDits password
IOS_MOBILE_PROVISIONthe provisioning profile, a .mobileprovision in base64
APPLE_API_ISSUERthe issuer id of the App Store Connect API key
APPLE_API_KEYthe key id
APPLE_API_KEY_P8the content of the .p8 file, written to APPLE_API_KEY_PATH

The three first ones enable the signed build; the three last ones the app-store-connect export and the upload. The keychain is deleted at the end of the job.

TestFlight and the App Store

  1. Create the application in App Store Connect with the bundle identifier of app:.
  2. Export with app-store-connect and upload: the workflow with upload, or Transporter by hand with the .ipa artifact.
  3. The build appears in TestFlight after Apple's processing; the store review needs the usual metadata, screenshots and a privacy answer (the application makes no network request of its own: the site is embedded).

Not verified yet

What the first run on a Mac must look at, and where to fix it:

  • the Xcode project's script phase finds the Tauri CLI: the workflow puts the one of @recit/app on PATH; on a Mac, run recit app ios init from the site so the CLI is resolved from there;
  • the scheme name ends with _iOS, as Tauri's template names it; the simulator step looks it up;
  • a simulator build without signing (CODE_SIGNING_ALLOWED=NO) goes through the script phase;
  • the .ipa lands in .recit/app/gen/apple/build/<arch>/, where the artifact and the upload look for it;
  • xcrun altool --upload-app still accepts uploads with an API key (Apple is retiring altool; Transporter is the fallback).

Limits

  • macOS only: recit app ios … lists the prerequisites and stops elsewhere.
  • Hot reload on a device needs the dev server reachable on the local network: devUrl is localhost, see the note on Android, which has the same question.
  • No deep link and no updater on iOS: the store updates the application.
  • Everything else is the desktop application: the same offline bundle, the same limits.