Skip to content

Commit dc316df

Browse files
committed
feat(ios): runtime orientation lock via BloomViewController
Register a BloomViewController subclass that overrides supportedInterfaceOrientations. When initWindow is called with width > height, the mask is set to landscape-only. This locks the app at runtime without restricting iPad orientations in the plist (which App Store requires to include all 4).
1 parent 7efbd07 commit dc316df

1 file changed

Lines changed: 40 additions & 4 deletions

File tree

native/ios/src/lib.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ static mut UI_VIEW: Option<Retained<AnyObject>> = None;
1919
static mut TOUCH_MAP: [*const c_void; 10] = [std::ptr::null(); 10];
2020
static mut BUNDLE_PATH: Option<String> = None;
2121
static mut SCREEN_SCALE: f64 = 1.0;
22+
/// UIInterfaceOrientationMask for the root view controller.
23+
/// Default: all (0x1E). Set to landscape (0x18) from bloom_init_window when width > height.
24+
static mut ORIENTATION_MASK: u64 = 0x1E; // UIInterfaceOrientationMaskAll
2225

2326

2427
/// Resolve a relative asset path to the app bundle path.
@@ -209,6 +212,29 @@ fn register_metal_view_class() {
209212
}
210213
}
211214

215+
// ============================================================
216+
// Register BloomViewController — UIViewController with orientation lock
217+
// ============================================================
218+
219+
unsafe extern "C" fn bloom_vc_supported_orientations(_this: *const c_void, _sel: Sel) -> u64 {
220+
unsafe { ORIENTATION_MASK }
221+
}
222+
223+
fn register_view_controller_class() {
224+
if AnyClass::get(c"BloomViewController").is_some() { return; }
225+
226+
unsafe {
227+
let superclass = AnyClass::get(c"UIViewController").unwrap();
228+
let cls = objc_allocateClassPair(superclass as *const AnyClass, b"BloomViewController\0".as_ptr(), 0);
229+
if cls.is_null() { return; }
230+
231+
let sel = Sel::register(c"supportedInterfaceOrientations");
232+
class_addMethod(cls, sel, bloom_vc_supported_orientations as *const c_void, b"Q16@0:8\0".as_ptr());
233+
234+
objc_registerClassPair(cls);
235+
}
236+
}
237+
212238
// ============================================================
213239
// Scene delegate — creates UIWindow + Metal view + wgpu engine
214240
// ============================================================
@@ -236,8 +262,9 @@ unsafe extern "C" fn scene_will_connect(
236262
let window: Allocated<AnyObject> = msg_send![window_cls, alloc];
237263
let window: Retained<AnyObject> = msg_send![window, initWithWindowScene: scene];
238264

239-
// Create UIViewController
240-
let vc_cls = AnyClass::get(c"UIViewController").unwrap();
265+
// Create BloomViewController (with orientation lock support)
266+
let vc_cls = AnyClass::get(c"BloomViewController")
267+
.unwrap_or_else(|| AnyClass::get(c"UIViewController").unwrap());
241268
let vc: Allocated<AnyObject> = msg_send![vc_cls, alloc];
242269
let vc: Retained<AnyObject> = msg_send![vc, init];
243270

@@ -345,6 +372,7 @@ unsafe extern "C" fn scene_will_connect(
345372
#[no_mangle]
346373
pub unsafe extern "C" fn perry_register_native_classes() {
347374
register_metal_view_class();
375+
register_view_controller_class();
348376
register_scene_delegate();
349377
}
350378

@@ -373,8 +401,9 @@ pub unsafe extern "C" fn perry_scene_will_connect(scene: *const c_void) {
373401
msg_send![w, initWithFrame: bounds]
374402
};
375403

376-
// Create UIViewController
377-
let vc_cls = AnyClass::get(c"UIViewController").unwrap();
404+
// Create BloomViewController (with orientation lock support)
405+
let vc_cls = AnyClass::get(c"BloomViewController")
406+
.unwrap_or_else(|| AnyClass::get(c"UIViewController").unwrap());
378407
let vc: Allocated<AnyObject> = msg_send![vc_cls, alloc];
379408
let vc: Retained<AnyObject> = msg_send![vc, init];
380409

@@ -512,8 +541,15 @@ fn pollster_block_on<F: std::future::Future>(future: F) -> F::Output {
512541
pub extern "C" fn bloom_init_window(_width: f64, _height: f64, title_ptr: *const u8, _fullscreen: f64) {
513542
let _title = str_from_header(title_ptr);
514543

544+
// Set orientation mask based on requested dimensions
545+
// width > height → landscape, otherwise all orientations
546+
if _width > _height {
547+
unsafe { ORIENTATION_MASK = 0x18; } // UIInterfaceOrientationMaskLandscape
548+
}
549+
515550
// Register ObjC classes for the scene delegate (window/view creation)
516551
register_metal_view_class();
552+
register_view_controller_class();
517553
register_scene_delegate();
518554

519555
// Signal the main thread that our ObjC classes are ready.

0 commit comments

Comments
 (0)