GEA on iOS
Your code becomes a UIKit project that Xcode builds and signs like any other iOS app. The view tree talks to the system directly, with no webview around it and no JavaScript bridge in the way.
npx create-geastack
The same components, rendered through UIKit
The same components that run on a microcontroller, on macOS or on a Linux panel reconcile to native UIKit views here. You keep one codebase, and each platform renders it with its own widgets.
The UI is ordinary TypeScript
These are the same GEA components you run on every other target. geatsc reads each one at build
time and lowers it to C++, which compiles into the Xcode target next to the Objective-C++ glue.
The app that ships carries no WKWebView, no JSContext and no
JavaScriptCore.
import { ReactiveComponent } from 'gea-embedded'
// One component, one reactive field. geatsc lowers count to a typed Signal;
// each tap runs a typed method whose write re-renders only the dependent node.
export class Counter extends ReactiveComponent {
count = 0
template() {
return (
<div class="counter">
<span class="count">{this.count}</span>
<button onClick={() => this.count++}>+</button>
</div>
)
}
}
Component nodes map to UIKit views
Each node in your component tree becomes a native UIKit view. The renderer keeps one view per node, keyed by node id, and picks the concrete class from the node's type: a Text node is a UILabel, a Button is a UIButton, an Image is a UIImageView, a scrolling list is a UIScrollView, a plain View is a UIView. The view is reused across frames and rebuilt only when the node's type changes.
// one native UIKit view per node, chosen by node type
if (isScrollableNode(node)) return [[GeaNativeScrollContainer alloc] initWithFrame:CGRectZero]; // UIScrollView
switch (type) {
case NodeType::Text: return [[GeaNativeLabel alloc] initWithFrame:CGRectZero]; // UILabel
case NodeType::Image: return [[UIImageView alloc] initWithFrame:CGRectZero]; // UIImageView
case NodeType::Canvas: return [[GeaCanvasView alloc] initWithFrame:CGRectZero]; // CoreGraphics
case NodeType::Button: return [GeaNativeButton buttonWithType:UIButtonTypeCustom]; // UIButton
default: return [[UIView alloc] initWithFrame:CGRectZero]; // View / list
}
On each frame GEA runs the component update, recomputes layout, and reconciles the tree onto those
views — frame, background, corner radius and transform set directly on the UIView, scrolling driven
by a real UIScrollView. The build goes through the GEA CLI: npx gea build --target ios
runs a normal Xcode build (xcodebuild) and produces a standard .app bundle
you install on the simulator or a device.
System frameworks, in TypeScript
The system frameworks are available from TypeScript under Apple's own names. The camera
(AVFoundation), maps and location (MapKit and CoreLocation), and the GPU (Metal) are the actual iOS
classes you would find in the documentation — AVCaptureSession, MKMapView,
CLLocationManager, MTLDevice — with the same properties and selectors. You
write against iOS as it is documented.
import { UIView, UIColor } from '@geastack/apple/UIKit'
const card = new UIView()
const inner = new UIView()
inner.backgroundColor = UIColor.systemBackgroundColor()
card.addSubview(inner)
Each call lowers straight to the Objective-C message it names — a constructor to
alloc/init, a method to its selector, a property to a property access on
the real object — with no generated C wrapper in between:
// card.addSubview(inner) lowers to a direct Objective-C++ message send
[((__bridge ::UIView *)gea::apple::objc::object(card.handle)) addSubview:
((__bridge ::UIView *)gea::apple::objc::object(inner.handle))];
Native views, written as JSX
Alongside the cross-platform GEA component model, you can write JSX directly against the real UIKit
classes, inline in your .tsx. The tags are the same documented iOS classes the bindings
expose — UIVisualEffectView, UILabel — with their real properties and
selectors. It reads like SwiftUI, but there is no SwiftUI underneath: it builds ordinary UIKit views.
A Vite plugin, appleNativeJsxPlugin from @geastack/vite-plugin-apple-native,
lowers each element to imperative construction — new Tag(), property assignments and
addSubview — so the declarative source and the native view tree stay one and the same.
The ios-native-showcase example is built this way.
import { CGRectMake } from '@geastack/apple/CoreGraphics'
import { UIBlurEffect, UIColor, UILabel, UIVisualEffectView, installRootView } from '@geastack/apple/UIKit'
// JSX over real UIKit classes → new UIVisualEffectView() + addSubview
const card = (
<UIVisualEffectView frame={CGRectMake(20, 150, 320, 180)} effect={UIBlurEffect.effectWithStyle(4)}>
<UILabel text="Native UIKit, written in TSX" textColor={UIColor.whiteColor()} />
</UIVisualEffectView>
) as UIVisualEffectView
installRootView(card)






