Makes the iOS build a first-class citizen: ROM/mod/save import through
the system document picker (the README's missing "UIDocumentPicker
handoff"), Files-app drop-in support, and an opt-in Apple Health
step-sync seam consumed by a new gallery mod (Pokewalker).
Native layer (mobile/ios/native/, wired by mobile/ios/patch_love_src.py
on every build, so the fetched love-src tree stays pristine + re-patchable):
- GRPickerBridge.swift: love.system.pickFile("rom"|"mod"|"sav") and
love.system.createFile on iOS with the same contract as love-android's
SAF picker (picked_rom.gb / picked_mod.zip / picked_save.sav /
export_done.flag in the save dir). Reached from wrap_System.cpp via the
ObjC runtime, so liblove needs no Swift interop.
- GRBootstrap.m: sweeps .gb/.gbc/.zip/.sav dropped in Documents (Files
app / Finder sharing) into the save dir on every activation;
UIFileSharingEnabled + LSSupportsOpeningDocumentsInPlace in the plist
overlay. Drop a ROM, open the app, it imports with zero taps.
- GRHealthBridge.swift: love.system.syncHealthSteps() -> read-only
HealthKit step query anchored to the last sync, delivered as
steps_pending.json (merge-not-overwrite). HealthKit entitlement +
usage description included.
Lua:
- RomImporter: iOS rides the Android mobile flows; a 0.5s poll consumes
picker deliveries (iOS pickers are in-process modals, so the Android
refocus rescan never fires); failed pick copies surface as an
on-screen notice via pick_error.txt.
- main.lua: on iOS, stop forwarding touchpressed to the Importer - LOVE
already synthesizes a mousepressed for the primary touch, and the
resulting same-frame double-present made the document picker
auto-dismiss with zero documents (silent import failure).
- mods/pokewalker: opt-in Pokewalker mod (manifest v2, MECHANIC,
permissions declared, mod.card, CHANGELOG, headless test suite 9/9,
modkit validate --base imported + lint clean). Fused into iOS
game.love only; loads dormant anywhere without the bridge.
Build (scripts/build_ios.sh):
- Fix Xcode 26: the global PRODUCT_NAME override also renamed liblove.a
and broke the app link; the app bundle is renamed after the build
instead.
- Fix nondeterministic pack failures: grep -q + pipefail races SIGPIPE
on the game.love content checks.
- Simulator builds sign ad-hoc so entitlements embed (HealthKit works in
the simulator).
- Device builds: signing team auto-detected from the keychain,
CODE_SIGN_STYLE=Automatic + -allowProvisioningUpdates for CLI-only
provisioning, per-team derived bundle ID (explicit App IDs are
globally unique, so third parties can't sign the project default),
gitignored mobile/ios/bundle_id.local pin, and --install to push to a
connected iPhone.
- docs/ios-install.md: a zero-knowledge walkthrough from bare Mac to
playing on an iPhone.
Backward compatibility: no behavior change on desktop or Android. The
new love.system functions exist only under LOVE_IOS; RomImporter's
mobile flag simply includes iOS alongside Android; the main.lua change
is iOS-gated; the Pokewalker mod is packed only by the iOS build script
and its option defaults off.
Verified on an iPhone 17 Pro simulator and an iPhone 16 Pro device:
scripted ROM import to title screen, Files-drop zero-tap import,
picker-driven mod install and save import/export, HealthKit permission
sheet + step credit (4000 steps -> +200 EXP at the default rate through
the engine growth curve).
iOS build (LÖVE 11.5)
Native ROM/mod/save import. The iOS build ships a Swift document-picker bridge (
native/GRPickerBridge.swift+GRBootstrap.m) thatpatch_love_src.pywires into the LÖVE tree on every build:
love.system.pickFile("rom"|"mod"|"sav")andlove.system.createFileare exposed to Lua on iOS (same contract as love-android's SAF picker: picks land in the save dir aspicked_rom.gb/picked_mod.zip/picked_save.sav; exports signal viaexport_done.flag).- The Info.plist overlay enables
UIFileSharingEnabled+LSSupportsOpeningDocumentsInPlace, andGRBootstrap.msweeps.gb/.gbc/.zip/.savfiles dropped in Documents (Files app / Finder) into the LÖVE save dir on every activation — drop a ROM, open the app, and it imports with no taps.src/import/RomImporter.luatreats iOS as a mobile platform and polls for picker results (iOS pickers are in-process modals, so Android's refocus rescan never fires).The note below about a missing "UIDocumentPicker handoff" is resolved by this bridge.
macOS + Xcode only. Pins the official LÖVE 11.5 iOS Xcode tree
(love-11.5-ios-source.zip from love2d/love releases),
matching conf.lua's t.version = "11.5".
There is no separate love2d/love-ios GitHub repo for 11.5; the release zip
is the vendored iOS project (Xcode project under
love-src/platform/xcode/love.xcodeproj, target love-ios).
Pin file: LOVE_VERSION → 11.5.
Quick start (simulator)
# Fetch LÖVE 11.5 iOS sources (once) + build for Simulator
scripts/build_ios.sh --fetch
The embedded game.love contains no ROM or generated game data. The current
first-boot importer has desktop file pickers only, so a production iOS release
still needs a UIDocumentPicker handoff that passes the selected ROM to LÖVE.
Default output: an unsigned Simulator .app under mobile/ios/build/
(no Apple Developer account required). A convenience copy also lands under
dist/ios/<Config>-<sdk>/.
Install on a booted simulator (example):
xcrun simctl install booted mobile/ios/build/Build/Products/Debug-iphonesimulator/PokemonRed.app
xcrun simctl launch booted com.theboisclub.pokemonred
Or open mobile/ios/love-src/platform/xcode/love.xcodeproj in Xcode,
select the love-ios target, and Run on a Simulator after
scripts/build_ios.sh --package-only (or a full build) has placed game.love.
Device / Release
scripts/build_ios.sh --device # Debug, physical device SDK
scripts/build_ios.sh --device --release # Release configuration
Device builds need a signing identity and provisioning profile configured in
Xcode (or via DEVELOPMENT_TEAM / CODE_SIGN_IDENTITY env vars). This repo
does not store certificates, profiles, or App Store Connect secrets.
Manual out-of-band steps:
- Apple Developer account + App ID for
com.theboisclub.pokemonred - Development or Distribution certificate + provisioning profile
- In Xcode: open
love.xcodeproj→ targetlove-ios→ Signing & Capabilities → select your Team (or setDEVELOPMENT_TEAM=XXXXXXXXXXwhen invokingscripts/build_ios.sh --device) - Archive / export an
.ipafrom Xcode Organizer for TestFlight / Ad Hoc
Layout
| Path | Role |
|---|---|
LOVE_VERSION |
Engine pin (11.5) |
overlays/love-ios.plist |
Portrait-only Info.plist + display name Pokemon Red (copied over the upstream plist every build) |
love-src/ |
Downloaded love-11.5-ios-source tree (gitignored, do not commit) |
cache/ |
Downloaded zips (gitignored) |
build/ |
xcodebuild derived data (gitignored) |
Game payload lands at:
love-src/platform/xcode/ios/resources/game.love
and is fused into the built .app (LÖVE auto-runs any bundled *.love).
Apple libraries dependency
The official love-11.5-ios-source.zip already ships prebuilt iOS
xcframeworks under platform/xcode/ios/libraries/ (SDL2, LuaJIT, freetype,
ogg, vorbis, theora, modplug).
If that folder is missing or incomplete (e.g. you cloned sources without libs), download the matching prebuilts and install them:
curl -fL -o mobile/ios/cache/love-11.5-apple-libraries.zip \
https://github.com/love2d/love/releases/download/11.5/love-11.5-apple-libraries.zip
unzip -q mobile/ios/cache/love-11.5-apple-libraries.zip -d mobile/ios/cache
rm -rf mobile/ios/love-src/platform/xcode/ios/libraries
cp -R mobile/ios/cache/love-apple-dependencies/iOS/libraries \
mobile/ios/love-src/platform/xcode/ios/libraries
scripts/build_ios.sh checks for libraries/SDL2.xcframework and fails with
these instructions if it is absent.
Upstream also documents love-apple-dependencies as an alternate source of the same libraries.
App identity
| Field | Value |
|---|---|
| Display name | Pokemon Red |
PRODUCT_NAME |
PokemonRed |
| Bundle ID | com.theboisclub.pokemonred |
| Orientations | Portrait only (UIInterfaceOrientationPortrait) |
Overrides are applied by the build script (xcodebuild settings + plist overlay)
so refreshing love-src/ does not lose branding.
Flags (scripts/build_ios.sh)
| Flag | Meaning |
|---|---|
| (default) | Simulator, Debug, no signing |
--fetch |
Download/extract love-11.5-ios-source.zip if love-src/ is missing |
--device |
Build against iphoneos instead of iphonesimulator |
--release |
Release configuration instead of Debug |
--package-only |
Zip game.love + apply plist overlay; skip xcodebuild |
Also: scripts/build.sh ios delegates here (--release is forwarded).
Preconditions
- macOS (Darwin) with Xcode +
xcodebuildonPATH - iOS platform installed in Xcode (Settings → Platforms).
xcodebuild -showsdksshould listiphonesimulator/iphoneos. A partial install can fail IB/xib compiles withiOS … Platform Not Installedeven when the SDK name appears. love-src/present (--fetchor manual unzip oflove-11.5-ios-source.zip)- iOS libraries under
love-src/platform/xcode/ios/libraries/(see above)