Native iOS Apps in PHP: TypePHP Compiles PHP to arm64 with UIKit Bridge
This article demonstrates how TypePHP enables native iOS app development entirely in PHP by compiling PHP to arm64 machine code and using a thin Objective-C++ bridge to UIKit, covering architecture, code structure, compilation, signing, deployment, and customization with concrete examples and commands.
Division of Labor: PHP Logic, Objective-C++ Thin Bridge
TypePHP development follows normal PHP conventions with one convention: declare a native function in the bridge file for each required iOS capability. The example app uses only six native functions: ui_app_run() – enter iOS application lifecycle ui_create_window() – create window, set logical canvas size ui_add_label() – add text label, return control ID ui_add_button() – add button, return control ID ui_set_control_text() – modify control text ui_show_window() – show window
The bridge is written in Objective-C++ ( .mm), belongs to the app, and is minimal. All UI layout, button actions, and state management remain in PHP. After compilation, the app contains no PHP source code—only machine instructions.
Code Structure
The official example resides in examples/apple-native/:
examples/apple-native/
├── php-src/
│ ├── application.php # UI and interaction logic (PHP)
│ └── ios-main.php # iOS entry point (PHP)
├── ios-src/
│ ├── uikit_bridge.stub.php # Native function declarations for PHP
│ └── uikit_bridge.mm # UIKit implementation (Objective-C++)
├── ios.yml # Compilation configuration
└── package-ios-app.sh # Packaging and signing scriptiOS Entry Point ( ios-main.php )
<?php
/** iOS launch completion, called by bridge */
function typephp_application_did_launch(): void
{
HelloApplication::build('UIKit');
ui_show_window();
}
/** Each control click triggers this callback with control ID */
function typephp_application_control_activated(int $controlId): void
{
HelloApplication::handleEvent($controlId);
}
function main(): void
{
ui_app_run('TypePHP iOS Hello');
}Layout in PHP ( buildPhoneLayout() )
private static function buildPhoneLayout(): void
{
ui_create_window('TypePHP Native Hello', 390, 844);
self::$titleLabel = ui_add_label('', 24, 672, 342, 76, 30, true);
self::$descriptionLabel = ui_add_label('', 30, 544, 330, 104, 16, false);
self::$statusLabel = ui_add_label('', 30, 454, 330, 58, 18, true);
self::$countButton = ui_add_button('', 30, 354, 330, 58, 1);
self::$resetButton = ui_add_button('', 30, 280, 158, 52, 2);
self::$languageButton = ui_add_button('', 202, 280, 158, 52, 3);
}Click Handling in PHP
public static function handleEvent(int $controlId): void
{
if ($controlId === self::$languageButton) {
self::$isChinese = !self::$isChinese;
self::renderText();
return;
}
if ($controlId === self::$resetButton) {
self::$clickCount = 0;
self::renderStatus();
return;
}
if ($controlId === self::$countButton) {
self::$clickCount++;
self::renderStatus();
}
} ui_add_label()and ui_add_button() return an auto-incrementing control ID. The bridge calls typephp_application_control_activated() with that ID on click, so event handling in PHP is a plain method call.
Compilation
Run from the TypePHP repository root using ios.yml as configuration:
export PHPX_HOME=/path/to/phpx
php bin/tpc.php examples/apple-native/ios.yml --no-progressThe output is examples/apple-native/typephp_ios_hello, verified as a Mach-O 64-bit arm64 executable targeting iOS 15.0:
$ file typephp_ios_hello
typephp_ios_hello: Mach-O 64-bit executable arm64
$ otool -l typephp_ios_hello | grep -A3 LC_BUILD_VERSION
cmd LC_BUILD_VERSION
platform 2 # iOS
minos 15.0Key ios.yml settings for iOS:
target-platform: arm64-apple-ios15.0 # cross-compile target
cpp-compiler: xcrun --sdk iphoneos clang++ # Xcode iPhoneOS toolchain
cxx-flags:
- -fobjc-arc # enable ARC for bridge
- -miphoneos-version-min=15.0
ld-flags:
- -framework UIKit
- -framework Foundation
- -framework CoreGraphicsPackaging and Signing
iOS requires code signing. Prepare a provisioning profile ( .mobileprovision) and developer certificate, then set environment variables:
export TYPEPHP_IOS_PROVISIONING_PROFILE=/path/to/profile.mobileprovision
export TYPEPHP_IOS_CODE_SIGN_IDENTITY='Apple Development: Your Name (TEAMID)'
# Override Bundle ID if profile differs from example
export TYPEPHP_IOS_BUNDLE_IDENTIFIER='your.provisioned.bundle.identifier'
sh examples/apple-native/package-ios-app.shThe script assembles the .app bundle, copies icons, signs, and verifies, outputting:
Created and signed .../dist/TypePHP iOS Hello.appInstall and Run on iPhone
List devices to obtain <device-id>: xcrun devicectl list devices Install the app:
xcrun devicectl device install app \
--device <device-id> \
'examples/apple-native/dist/TypePHP iOS Hello.app'Launch on device (or tap the icon):
xcrun devicectl device process launch --device <device-id> swoole.typephpAfter first install, trust the developer certificate in Settings → General → VPN & Device Management.
Customizing Your Own UI
Coordinates use a logical canvas with origin at top-left; example uses 390 x 844 (iPhone portrait). The bridge converts to UIKit coordinates and scales automatically for screen size and notch. ui_add_label($text, $x, $y, $w, $h, $fontSize, $bold) – text control. ui_add_button($title, $x, $y, $w, $h, $style) – button; style: 1 (solid blue), 2 (light blue), 3 (gray). ui_set_control_text($id, $text) – update text for live status.
Button clicks unified via typephp_application_control_activated($id) callback; differentiate by ID.
Example: add a "Greet" button:
// Layout
self::$helloButton = ui_add_button('打个招呼', 30, 200, 330, 52, 2);
// Event
if ($controlId === self::$helloButton) {
ui_set_control_text(self::$statusLabel, '你好,TypePHP!');
}For capabilities beyond UIKit (location, camera, network), add a php_ -prefixed function in uikit_bridge.mm and declare it in .stub.php; PHP can then call it directly.
Important Notes
Real device only. Current iOS target provides only iphoneos arm64; no simulator target, signing mandatory.
Bridge is hand-written, not auto-generated. Unlike Android/JNI reflection bridges, each app maintains its own .mm file—fully controllable and small, but requires writing a function for each system capability used.
Provisioning profiles expire. Personal developer profiles typically last 7 days; re-issue via Xcode when expired.
Build environment: macOS + full Xcode + matching fully static libphp.a / libphpx.a (located at PHPX_HOME/ios/iphoneos-arm64).
The same application.php, with a different entry and bridge, can compile to a macOS AppKit app; Android also has an arm64 native solution. One PHP codebase targeting native UI on desktop and mobile —a new direction for TypePHP beyond the server.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
