From 6898e141d8ebbd24929cdac9f8b5445c4850b1bd Mon Sep 17 00:00:00 2001 From: Rob King Date: Sun, 2 Aug 2026 00:42:28 +0100 Subject: [PATCH] fix: Emit interface orientation when updates start and on didBecomeActive On a cold launch the window scene is not yet foregroundActive when HybridInterfaceOrientationManager reads the interface orientation - during init() and potentially still when startOrientationUpdates() runs - so it resolves to .unknown (treated as portrait). The manager then only updates on UIDevice.orientationDidChangeNotification, which never fires until the device is physically rotated. Result: an app cold-launched in landscape (common on iPad) renders the preview and all outputs rotated 90 degrees until the user rotates the device once. Reproducible with both orientationSource values. Fix: emit the current interface orientation when orientation updates start, and again on UIApplication.didBecomeActiveNotification - the moment the scene state is guaranteed correct. Verified on iPad mini (6th gen), iPadOS 26: cold landscape launch now renders correctly from the first frame. Co-Authored-By: Claude Fable 5 --- .../HybridInterfaceOrientationManager.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift index 4a37053bb6..e5003bf610 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift @@ -12,6 +12,7 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { let source: OrientationSource = .interface private(set) var currentOrientation: CameraOrientation? = nil private var observer: NSObjectProtocol? = nil + private var didBecomeActiveObserver: NSObjectProtocol? = nil override init() { super.init() @@ -34,6 +35,34 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { // Start new listener (beginGeneratingDeviceOrientationNotifications() can be nested) UIDevice.current.beginGeneratingDeviceOrientationNotifications() + // On a cold launch the window scene is not yet foregroundActive - during + // init() and potentially still at this point - so interfaceOrientation + // resolves to .unknown (treated as portrait), and orientationDidChange + // does not fire until the device physically rotates. An app launched in + // landscape therefore renders all outputs 90° rotated until the first + // rotation. Emit the current orientation now (in case the scene is + // already active), and again when the app becomes active - the moment + // the scene state is guaranteed to be correct. + let emitCurrentOrientation: () -> Void = { [weak self] in + guard let self else { return } + let interfaceOrientation = UIApplication.shared.interfaceOrientation + guard interfaceOrientation != .unknown else { return } + let orientation = CameraOrientation(interfaceOrientation: interfaceOrientation) + if self.currentOrientation != orientation { + logger.info("Interface orientation resolved: \(orientation.stringValue)") + self.currentOrientation = orientation + onChanged(orientation) + } + } + emitCurrentOrientation() + self.didBecomeActiveObserver = NotificationCenter.default.addObserver( + forName: UIApplication.didBecomeActiveNotification, + object: nil, + queue: .main + ) { _ in + emitCurrentOrientation() + } + self.observer = NotificationCenter.default.addObserver( forName: UIDevice.orientationDidChangeNotification, object: nil, @@ -62,6 +91,10 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { NotificationCenter.default.removeObserver(observer) UIDevice.current.endGeneratingDeviceOrientationNotifications() } + if let didBecomeActiveObserver = self.didBecomeActiveObserver { + NotificationCenter.default.removeObserver(didBecomeActiveObserver) + self.didBecomeActiveObserver = nil + } } } }