Skip to content

Building the iOS app

To build the iOS app you need a Mac with Xcode (26 or later) and Node 22 or later; Capacitor's packages come through Swift Package Manager (no CocoaPods):

cd mobile
npm ci
npx cap sync ios
open ios/App/App.xcodeproj        # then Run, on a simulator or a phone
# or, without Xcode's window, for a simulator:
cd ios/App && xcodebuild -scheme App -configuration Debug -destination 'generic/platform=iOS Simulator' \
    -derivedDataPath build CODE_SIGN_IDENTITY=- build
xcrun simctl install booted build/Build/Products/Debug-iphonesimulator/App.app

To try CarPlay without a car, run the app on a simulator and open I/O → External Displays → CarPlay; with a phone, Apple's CarPlay Simulator (Xcode's Additional Tools) plays the car.

Debug builds are Nessie Dev (io.github.jonp92.nessie.debug), as on Android. A phone needs the app signed with your Apple ID (Xcode → Signing & Capabilities). A simulator build needs signing too (CODE_SIGN_IDENTITY=-, signed to run locally, as Xcode does by itself), or background downloads fail: iOS knows the app by its signature. The web view can be inspected from Safari on the Mac (Develop menu), and scripted with ios-webkit-debug-proxy (brew install ios-webkit-debug-proxy), which speaks a Chrome-like protocol on localhost:9222.

The queue, the play journal, the car's ids, and the rules for kept files and the kept copy are plain Swift, tested on the Mac without a phone:

cd mobile/ios/NessieTests && swift test

TestFlight

The household gets the iOS app through TestFlight, as internal testers (people added in App Store Connect → Users and Access, then to the app's internal group): their builds need no review by Apple and arrive minutes after upload. (External testers would need Apple's beta review, and its reviewers a Nessie server to try.) A TestFlight build works for 90 days, so upload one with each release.

Once, on the Mac: sign in to Xcode (Settings → Accounts) with the developer account, whose team (DEVELOPMENT_TEAM) is in the project; and in App Store Connect, add an app for the bundle ID io.github.jonp92.nessie (its store name must be unique on the App Store, so it may differ from "Nessie", the name under the icon).

Each release, after tagging it:

scripts/ios-testflight.sh v0.12.0

It copies the app as committed at that tag to the Mac (NESSIE_MAC, an SSH address such as [email protected]; into ~/nessie-release), installs and syncs it, sets the version to the tag's and the build number to the time (each upload needs a higher one), and opens it in Xcode. There: Any iOS Device, Product → Archive, then Distribute App → TestFlight & App Store. Archiving happens in Xcode's window because signing needs the Mac's keychain, which an SSH session can't unlock.

PrivacyInfo.xcprivacy tells Apple what the app does with data (nothing collected or tracked) and why it uses the APIs Apple asks about: its own settings (UserDefaults) and the free space the played songs' limit is worked out from. Using another such API means adding it there, or App Store Connect turns the upload down. ITSAppUsesNonExemptEncryption is false (only the HTTPS iOS provides), so it doesn't ask about encryption with each build.