C+
iOS

A native iOS screen in C+

This is a real iOS app: a UIWindow with a white background and a centered label, with the UI written in C+ through the uikit bindings. It cross-compiles with one flag, Xcode links it like any static library, and it renders on the simulator. The only C in the project is a two-line main shim.

uikit mirrors appkit: real UIView objects over ObjC-runtime FFI, closure-free callbacks, and owned wrappers that release in drop. iOS is a cross-compile target, so cpc stops at the object and Xcode owns the final link — see Targets & cross-compilation.

How an iOS app starts

UIApplicationMain never returns, so the flow is: the C+ app exports cplus_app_main, which hands off to application::run with a didFinishLaunchingWithOptions: implementation. That callback builds the window and views and returns 1.

Full source

src/main.cplus — the whole app:

// A native iOS screen, with the UI in C+. Cross-compiled with
// `cpc build --target ios-arm64-simulator`; Xcode links it and owns main.

import "uikit/application" as application;
import "uikit/window" as window;
import "uikit/controllers" as controllers;
import "uikit/view" as view;
import "uikit/screen" as screen;
import "uikit/runtime" as rt;

// `application:didFinishLaunchingWithOptions:`. Build the UI here, return 1.
fn did_finish(_self: *u8, _cmd: *u8, _app: *u8, _options: *u8) -> i8 {
    let bounds: rt::Rect = screen::Screen::main().bounds();

    // Root view controller with a white view.
    let vc: controllers::ViewController = controllers::ViewController::new();
    vc.view().set_background_color(view::Color::white());

    // A centered label, half-way down the screen.
    let label: view::Label = view::Label::new(
        rt::make_rect(0.0, bounds.size.height / 2.0 - 20.0, bounds.size.width, 40.0));
    label.set_text("Hello from C+");
    label.set_text_alignment(1 as i64);            // NSTextAlignmentCenter
    label.set_text_color(view::Color::black());
    vc.view().add_subview(label.obj);

    // The key window owns the controller; UIKit keeps it for the process.
    let w: window::Window = window::Window::new(bounds);
    w.set_root_view_controller(vc);
    w.make_key_and_visible();

    return 1;
}

// Exported entry point. Xcode's main.c shim calls this (see Reproduce).
pub extern fn cplus_app_main(argc: i32, argv: *u8) -> i32 {
    return application::run(argc, argv, did_finish);
}

There are no closures: did_finish is a plain named function handed to application::run, exactly the way UIKit wants an app-delegate method. Widgets are real UIViews; label.obj is the underlying id handed to add_subview.

Results

Built with cpc build --target ios-arm64-simulator and linked by Xcode.

Build output an iOS staticlib (object + archive + C header) in target/ios-arm64-simulator/debug/
Final link Xcode (the iOS targets stop at object emission)
On the simulator a white screen with a centered "Hello from C+", on the iPhone 16 Pro simulator
C in the project a two-line main shim — everything else is C+

The point: the screen you see is driven by C+ calling UIKit directly, with no binding-generator and no glue layer, the same model appkit uses on the desktop.

Reproduce

You need cpc 0.0.21 or newer and Xcode (for the iOS SDK and the final link).

1. The C+ static library. Put the source above in src/main.cplus with a manifest that builds a staticlib and depends on uikit:

[package]
name    = "hello_ios"
version = "0.0.1"
edition = "2026"

[lib]
name = "app"

[dependencies]
uikit = "*"
cpc build --target ios-arm64-simulator     # or ios-arm64 for a device

cpc emits the object, archive (libapp.a), and a C header; it never runs the link.

2. The two-line shim. In the Xcode app target, a single C file enters the C+ app:

extern int cplus_app_main(int argc, char **argv);
int main(int argc, char **argv) { return cplus_app_main(argc, (void *)argv); }

3. Link in Xcode. Add libapp.a to the target's Link Binary With Libraries, along with UIKit, Foundation, and libobjc (the frameworks the [link] table names belong on Xcode's link line). Build and run on the simulator; the C+-driven screen appears.


‹ Back to all examples