Skip to content

🐛 iOS orientationSource="interface" does not update on programmatic UI orientation locks (Android does) #4109

Description

@isinuyk

Prerequisites

Reproduction

https://github.com/ivansinuyk/vision-camera-ios-interface-orientation-repro

Steps to reproduce

Reproduction (simplified)

  1. App starts in portrait.
  2. Navigate to a camera screen.
  3. Immediately lock UI to landscape (OrientationDirector.lockTo(landscapeRight) or equivalent).
  4. Mount without waiting for the rotation animation.
  5. Android: preview/output becomes landscape.
  6. iOS: preview/output often stays portrait (rotated 90°) even though the UI is landscape.
    If Camera is mounted only after the lock animation finishes, iOS looks fine because init reads the already-updated interfaceOrientation once — but that is an app workaround, not correct continuous listening.

What did you expect to happen?

With orientationSource="interface":

  • When the app UI rotates to landscape (including programmatic locks), VisionCamera should update outputOrientation / preview to match the new interface orientation.

  • Behavior should match the docs: interface mode follows UI orientation / screen lock, not only physical tilt.

What actually happened?

Android | Works. Display rotation changes → camera orientation updates.

iOS | Often stuck. Programmatic landscape lock updates UI, but VisionCamera does not update until something else triggers a refresh (or Camera is remounted after rotation completes).

Affected platforms

iOS (device)

Device(s) affected

iPhone 17 Pro Max

VisionCamera version

5.1.1

React Native version

0.86.0

React Native architecture

New Architecture (Fabric / bridgeless)

Features being used

  • Preview
  • Photo capture
  • Video capture
  • Frame Processors (worklets)
  • Skia Frame Processors
  • Code/Barcode Scanner
  • Location metadata
  • Multi-cam
  • Depth data
  • HDR / custom dynamic range
  • Custom format / FPS / resolution

Relevant logs / stack trace

No crash — behavioral bug. Logs from physical iPhone while reproducing
programmatic landscape lock + VisionCamera.createOrientationManager('interface')

Additional context

On Android, orientationSource="interface" correctly follows app UI orientation changes driven by a programmatic lock (e.g. locking the screen to landscape when opening a camera screen).

On iOS, the same setup often keeps the camera preview/output stuck in the previous orientation (commonly portrait) when the app locks to landscape via an orientation locker library. Physical device rotation still works; programmatic interface locks do not.

This forces apps to delay mounting on iOS until after the UI rotation settles — Android does not need that workaround.

Android – HybridInterfaceOrientationManager listens to DisplayManager.DisplayListener.onDisplayChanged and reads display rotation. Activity orientation locks update the display, so the camera follows.

iOS – HybridInterfaceOrientationManager listens to UIDevice.orientationDidChangeNotification (physical device tilt), then reads UIApplication.shared.interfaceOrientation.

That notification does not reliably fire for programmatic interface locks (requestGeometryUpdate / orientation director). So UI becomes landscape while VisionCamera never receives an update and keeps the old orientation.

Relevant iOS code (v5.1.1):

// HybridInterfaceOrientationManager.swift
self.observer = NotificationCenter.default.addObserver(
  forName: UIDevice.orientationDidChangeNotification, // physical device
  object: nil,
  queue: .main
) { _ in
  let interfaceOrientation = UIApplication.shared.interfaceOrientation // UI
  // ...
}

So iOS “interface” mode is driven by a device signal, while Android “interface” mode is driven by a display/UI signal.

What we need
Please make iOS OrientationSource.interface track UI / interface orientation changes, including programmatic locks, similar to Android’s display listener.

Suggested approach (any of these):

Stop relying solely on UIDevice.orientationDidChangeNotification for interface mode.
Also observe real interface-orientation transitions (and/or poll windowScene.interfaceOrientation while updates are active).
Emit/sync current UIApplication.shared.interfaceOrientation when startOrientationUpdates begins, and again whenever it changes due to geometry updates / locks.
Workaround we verified
A local patch that:

keeps the existing device-orientation observer
also observes UIApplication.didChangeStatusBarOrientationNotification
polls UIApplication.shared.interfaceOrientation at a low rate while updates are active
…fixes iOS and lets us remove the “delay Camera mount until lock settles” workaround.

Happy to open a PR if useful.

You can attach your patch file

react-native-vision-camera-npm-5.1.1-93b71ffaa4.patch

) or a short screen recording of iOS vs Android if you want to make it stronger.

Submission

  • The reproduction I linked is either (preferred) a PR against this repo that adds a failing harness test following the harness-tests README, or (fallback) a public repo that reproduces the bug on a fresh clone. I understand the issue will be closed without one.
  • I pasted logs as text (not screenshots).
  • I wrote this report in my own words. I did not paste AI-generated descriptions of the bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions