# iOS

The [desktop application](/documentation/configuration/app) 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.

:::note{title="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](#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

| Key                   | Default                           | Meaning                                             |
| --------------------- | --------------------------------- | --------------------------------------------------- |
| `ios.developmentTeam` | none, or `APPLE_DEVELOPMENT_TEAM` | the team that signs the application                 |
| `identifier`          | see the app page                  | becomes the bundle identifier, final                |
| `icon`                | `metadata.favicon`                | the `AppIcon` set is derived from it                |
| `version`             | the site's `package.json`         | the 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

| Command                                                    | Effect                                                                                                          |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `recit app ios init`                                       | generates the Xcode project once, in `.recit/app/gen/apple/` (ignored by git); `-- --ci` skips the prompts      |
| `recit app ios dev`                                        | runs the application in the simulator, or on a device, against `recit-site dev`, with hot reload                |
| `recit app ios build -- --export-method debugging`         | a signed `.ipa` for the devices of the team, in `.recit/app/gen/apple/build/`                                   |
| `recit app ios build -- --export-method app-store-connect` | a signed `.ipa` for TestFlight and the App Store; `release-testing` is the ad hoc export for registered devices |
| `recit app build --target ios`                             | the 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:

```bash
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`.

| Secret                     | Content                                                        |
| -------------------------- | -------------------------------------------------------------- |
| `APPLE_DEVELOPMENT_TEAM`   | the team id (ten characters)                                   |
| `IOS_CERTIFICATE`          | the signing certificate, a `.p12` in base64                    |
| `IOS_CERTIFICATE_PASSWORD` | its password                                                   |
| `IOS_MOBILE_PROVISION`     | the provisioning profile, a `.mobileprovision` in base64       |
| `APPLE_API_ISSUER`         | the issuer id of the App Store Connect API key                 |
| `APPLE_API_KEY`            | the key id                                                     |
| `APPLE_API_KEY_P8`         | the 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](/documentation/configuration/app-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](/documentation/configuration/app#limits).
