Skip to content

Native iOS

laqieer edited this page Jul 22, 2026 · 3 revisions

Native iOS

FEBuilderGBA has an experimental native iOS / iPadOS port built on the cross-platform Avalonia GUI — the iOS counterpart of the Native Android head. It is a real .ipa that runs the shared Avalonia UI on-device, reusing the same platform-agnostic seams the Android port introduced.

Authoritative reference: the full design and status lives in the in-repo docs/IOS.md. This page is a summary.

What it is

  • A FEBuilderGBA.iOS/ application head (AppDelegate : AvaloniaAppDelegate<App>) that builds and packages an .ipa consuming the same Avalonia views as the desktop GUI.
  • The shared FEBuilderGBA.Avalonia project multi-targets net10.0;net10.0-ios.
  • Built on top of FEBuilderGBA.Core (platform-independent) + FEBuilderGBA.Avalonia (shared UI). WinForms is not iOS-capable and is excluded.

Multi-target opt-in (EnableIosTarget)

The iOS target framework is opt-in and OFF by default, so the desktop solution and all desktop CI keep seeing net10.0 only and are completely untouched (mirrors Android's EnableAndroidTarget):

dotnet workload install ios
dotnet build FEBuilderGBA.iOS/FEBuilderGBA.iOS.csproj -c Release -p:EnableIosTarget=true

The -p:EnableIosTarget=true global property is required so NuGet restore cross-targets the shared project too. iOS builds require macOS + Xcode (currently Xcode 26.5, provided by the macos-26 runner in CI). The iOS head is deliberately excluded from FEBuilderGBA.sln — the required desktop build check runs the whole solution on a Windows runner with no iOS workload, so adding the head to the .sln would break unrelated PRs.

Key engineering pieces

Area How it works
config/ bundling The config/ tree ships as a BundleResource (with LogicalName preserving the config/<subpath> structure) and is version-stamped, extracted on first run into an app-private writable dir (Library/febuildergba), via the pure, desktop-unit-tested AndroidConfigExtractorCore + DirectoryAssetSource. The iOS bundle is read-only, so config must live in a writable location.
ROM file I/O Stream-based via Avalonia IStorageProvider — the same seam the Android port added for SAF — so security-scoped iOS document picks work without a plain file path.
Navigation Reuses the Android single-view shell (INavigationService + page/view-stack host) under ISingleViewApplicationLifetime; no iOS-specific UI code was needed.
SkiaSharp pin SkiaSharp.NativeAssets.iOS is pinned to 2.88.9 to match Avalonia's bundled native Skia — a 3.x managed package against an 88.x native crashes inside the Avalonia process.
Runtime viability iOS Release is full AOT + trimmed, and the Core/ROM pipeline is reflection-heavy, so the head enables the Mono interpreter (UseInterpreter=true) and links only the SDK (MtouchLink=SdkOnly) to maximize the chance the shipped .ipa actually runs.

Unsigned .ipa / how to install

The CI/release .ipa is unsigned (there is no Apple Developer signing on this project), so it is NOT directly installable on a stock device. To run it you must re-sign it with your own Apple ID using a sideloading tool:

  • AltStore or Sideloadly (re-sign with your Apple ID), or Apple Configurator.
  • Download FEBuilderGBA-ios-unsigned-ipa.zip from the latest release (attached when the macOS build succeeds — it is a soft/advisory asset), unzip to get the .ipa, then sideload it.

When a maintainer adds the APPLE_* GitHub Actions secrets, CI switches to a release-signed build (see docs/IOS.md §6).

CI

  • ios.yml — advisory (non-required, macOS runner) build that produces the unsigned .ipa and verifies the bundled config/ tree survived. Cannot block PR merges.
  • release.yml — a soft ios job (continue-on-error, not in the mandatory verify-assets list) attaches FEBuilderGBA-ios-unsigned-ipa.zip to a ver_* release when the build succeeds, and degrades to "release without iOS" if it doesn't.

Current limitations

The honest status: the .ipa builds and its config bundling is verified in CI, but the on-device runtime is not yet validated:

  • Touch interaction, file pickers, and reaching/using every editor are unproven on a real device (iOS AOT/trim adds a runtime dimension Android's Mono JIT does not).
  • The ~24 per-editor OperatingSystem.IsAndroid() file-flow guards in the shared UI are not yet extended to iOS (build-safe; they take the untested desktop path at runtime) — a documented follow-up.
  • Like Android, config/patch2 (binary-patch library) and the FE-Repo graphics/music resources are not bundled (large git-delivered payloads).

See docs/IOS.md for the layer-by-layer detail and build prerequisites.

See also

Clone this wiki locally