This describes how to build a standalone, offline-capable build of the app and install it directly onto a physical iPhone — useful for field testing without a laptop/Metro connection.
The build uses the Release configuration, which embeds the Hermes JS bundle into the app, so it runs without the Metro bundler. For store / TestFlight releases, use EAS instead (see Releases.md).
- macOS with Xcode installed.
- An Apple Developer account added to Xcode (Xcode → Settings → Accounts) and an "Apple Development" signing identity in your keychain.
- The iPhone connected via USB, paired and trusted, with Developer Mode enabled (Settings → Privacy & Security → Developer Mode → on, then reboot).
- Project dependencies installed (
npm install).
APP_VARIANT selects the variant (app name / bundle id). releaseCandidate
produces "CoMapeo RC" (com.comapeo.rc); other values are development
(.dev), preRelease (.pre), and production.
APP_VARIANT=releaseCandidate npx expo prebuild --platform iosAdd --clean if you've changed app.json /
app.config.js or hand-edited the ios/ directory. The
config plugin bakes the default categories (defaultConfig) into the app during
this step.
# Device identifier (and pairing/Developer Mode state)
xcrun devicectl list devices
# Signing team id — the (XXXXXXXXXX) in the identity name, i.e. the cert's OU
security find-identity -v -p codesigningcd ios
xcodebuild -workspace CoMapeoRC.xcworkspace -scheme CoMapeoRC \
-configuration Release \
-destination 'generic/platform=iOS' \
-derivedDataPath ./build \
-allowProvisioningUpdates \
DEVELOPMENT_TEAM=<YOUR_TEAM_ID> \
CODE_SIGN_STYLE=Automatic \
build
cd ..-
-destination 'generic/platform=iOS'builds for a generic device and avoids the "developer disk image could not be mounted" error you get when targeting the connected device directly for a build. -
-allowProvisioningUpdateslets Xcode register the device and create a development provisioning profile automatically. -
If SwiftPM fails with "Couldn't get the list of tags" (resolving the MapLibre package), resolve once and rebuild:
xcodebuild -workspace ios/CoMapeoRC.xcworkspace -scheme CoMapeoRC \ -derivedDataPath ios/build -resolvePackageDependencies
If it still fails with "cannot use bare repository ... (safe.bareRepository is 'explicit')", your global git has
safe.bareRepository = explicit, which blocks SwiftPM's bare package caches. Override it for the build process only (don't change your global config) by prefixing thexcodebuildcommands with:GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.bareRepository GIT_CONFIG_VALUE_0=all \ xcodebuild ...
xcrun devicectl device install app \
--device <DEVICE_ID> \
ios/build/Build/Products/Release-iphoneos/CoMapeoRC.appxcrun devicectl device process launch --device <DEVICE_ID> com.comapeo.rc- The build runs untethered — disconnect and take the device into the field.
- The location permission prompt is shown only once per install. To re-test the onboarding / permission flow, delete the app from the device first.
- Development-signed builds from a paid team are valid for a year; from a free personal team, ~7 days (after which they must be reinstalled).