Goal
Add a UIKit backend for iOS — a fourth implementation of the existing cross-platform widget ABI in backend/aether_ui_backend.h, alongside GTK4 (Linux/FreeBSD), AppKit (macOS, backend/aether_ui_macos.m), and Win32. The payoff: an aether-ui app written once in the trailing-block DSL runs on Linux, macOS, Windows, and now iOS — the whole app including the UI is Aether, wrapped only in a minimal Swift/Obj-C launch shell.
This is why the design already supports it: aether-ui is factored as a portable Aether DSL over a ~260-function backend ABI with pluggable native implementations. iOS is a new backend, not a rewrite of the UI layer. The AppKit backend (backend/aether_ui_macos.m, ~5,900 lines of Obj-C) is the closest reference — UIKit shares concepts with AppKit but differs in class names and layout.
Prerequisite
Depends on core Aether shipping an iOS --emit=lib target so this backend .m can be linked into an iOS app: aether-lang-dev/aether#1385 ("iOS target: --emit=lib static archive for aarch64-ios / -simulator"). That issue handles the toolchain (Tier-B SDK sysroot), Mach-O platform load commands, and stubbing App-Store-forbidden syscalls (fork/exec/spawn). This issue is the UI half.
Work
backend/aether_ui_uikit.m — implement the aether_ui_backend.h ABI (~260 _impl entry points) against UIKit:
- Widgets: map the widget kinds to
UILabel / UIButton / UIStackView / UITextField / UITableView / UIImageView / etc. (Group 1/2 widgets in the header).
- Layout — the biggest impedance mismatch: GTK's box model and AppKit's
NSStackView both differ from UIKit's Auto Layout / UIStackView. The DSL's layout intent (weights via aether_ui_widget_weight_impl, frames in ui/frames.ae) must map to Auto Layout constraints. Expect this to be the bulk of the effort.
- Events: the ABI's closure dispatch (incl. the double-click closure at header L63, hover, context menus) maps to UIKit gesture recognizers / target-action. Touch vs. pointer: some interactions have no touch analog — hover and right-click/context-menu (
aether_ui_menu_popup) degrade or remap to long-press; document the mapping.
- Styling readback:
aether_ui_widget_kind_impl / _parent_impl / _classes_impl and the CSS-alike apply/add/remove-class calls (header L156-275) — the DSL walks the live tree in-process, so these must reflect the real UIKit view tree.
- Accessibility: map the role/name/description ABI (header L215-220) to
UIAccessibility (AppKit maps to NSAccessibility, GTK to GtkAccessible, Win32 to MSAA — UIKit is the iOS analog).
- Canvas / images:
aether_ui_canvas_get_widget, the in-memory image decode path (header L292-311) → UIImage(data:) + a CALayer/UIView draw surface.
- Windows/menus: iOS has no menu bar and a different window/scene model (
UIWindow + view controllers, safe-area, rotation). aether_ui_widget_window_impl and menu APIs map awkwardly — decide per-API whether to support, remap, or no-op, and document.
The launch shell (small, in the consuming app / an example): a UIApplicationMain + AppDelegate whose didFinishLaunchingWithOptions hands off to the Aether entry point that builds the widget tree via the DSL. Xcode owns Info.plist + signing + bundling.
An example app + a simulator smoke test under apps/ or examples/, mirroring an existing small app, verified in the iOS simulator.
Caveats (genuine platform-shaping, not blockers)
- AppKit → UIKit is not copy-paste: no menu bar, touch not mouse, view-controller lifecycle, safe-area/rotation. The DSL stays identical; the backend does the shaping.
- App Store sandbox restrictions (no child processes, restricted networking) come from core Aether#1385's stubs — orthogonal to the UI, but a UI app that expected e.g. shelling out won't get it on iOS.
Acceptance
An existing small aether-ui app (or a new minimal one) builds against aether_ui_uikit.m + the iOS --emit=lib archive and runs in the iOS simulator: widgets render, layout responds to rotation/safe-area, tap events fire their DSL closures, with hover/right-click interactions degraded to documented touch equivalents.
Goal
Add a UIKit backend for iOS — a fourth implementation of the existing cross-platform widget ABI in
backend/aether_ui_backend.h, alongside GTK4 (Linux/FreeBSD), AppKit (macOS,backend/aether_ui_macos.m), and Win32. The payoff: an aether-ui app written once in the trailing-block DSL runs on Linux, macOS, Windows, and now iOS — the whole app including the UI is Aether, wrapped only in a minimal Swift/Obj-C launch shell.This is why the design already supports it: aether-ui is factored as a portable Aether DSL over a ~260-function backend ABI with pluggable native implementations. iOS is a new backend, not a rewrite of the UI layer. The AppKit backend (
backend/aether_ui_macos.m, ~5,900 lines of Obj-C) is the closest reference — UIKit shares concepts with AppKit but differs in class names and layout.Prerequisite
Depends on core Aether shipping an iOS
--emit=libtarget so this backend.mcan be linked into an iOS app: aether-lang-dev/aether#1385 ("iOS target: --emit=lib static archive for aarch64-ios / -simulator"). That issue handles the toolchain (Tier-B SDK sysroot), Mach-O platform load commands, and stubbing App-Store-forbidden syscalls (fork/exec/spawn). This issue is the UI half.Work
backend/aether_ui_uikit.m— implement theaether_ui_backend.hABI (~260_implentry points) against UIKit:UILabel/UIButton/UIStackView/UITextField/UITableView/UIImageView/ etc. (Group 1/2 widgets in the header).NSStackViewboth differ from UIKit's Auto Layout /UIStackView. The DSL's layout intent (weights viaaether_ui_widget_weight_impl, frames inui/frames.ae) must map to Auto Layout constraints. Expect this to be the bulk of the effort.aether_ui_menu_popup) degrade or remap to long-press; document the mapping.aether_ui_widget_kind_impl/_parent_impl/_classes_impland the CSS-alike apply/add/remove-class calls (header L156-275) — the DSL walks the live tree in-process, so these must reflect the real UIKit view tree.UIAccessibility(AppKit maps toNSAccessibility, GTK toGtkAccessible, Win32 to MSAA — UIKit is the iOS analog).aether_ui_canvas_get_widget, the in-memory image decode path (header L292-311) →UIImage(data:)+ aCALayer/UIViewdraw surface.UIWindow+ view controllers, safe-area, rotation).aether_ui_widget_window_impland menu APIs map awkwardly — decide per-API whether to support, remap, or no-op, and document.The launch shell (small, in the consuming app / an example): a
UIApplicationMain+AppDelegatewhosedidFinishLaunchingWithOptionshands off to the Aether entry point that builds the widget tree via the DSL. Xcode ownsInfo.plist+ signing + bundling.An example app + a simulator smoke test under
apps/orexamples/, mirroring an existing small app, verified in the iOS simulator.Caveats (genuine platform-shaping, not blockers)
Acceptance
An existing small aether-ui app (or a new minimal one) builds against
aether_ui_uikit.m+ the iOS--emit=libarchive and runs in the iOS simulator: widgets render, layout responds to rotation/safe-area, tap events fire their DSL closures, with hover/right-click interactions degraded to documented touch equivalents.