Problem
InterfaceOrientationManager.updateSupportedInterfaceOrientations() at InterfaceOrientationManager.swift:168-178 uses .first on UIApplication.shared.connectedScenes to find the window scene:
private func updateSupportedInterfaceOrientations() {
if #available(iOS 16.0, *) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return Self.logger.error("No active window scene found")
}
windowScene.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
} else {
UIViewController.attemptRotationToDeviceOrientation()
}
}
This has three issues:
- Non-deterministic selection:
connectedScenes is a Set<UIScene>, meaning .first has no guaranteed ordering. It may grab a background scene rather than the active one.
- iPad multi-window: In iPad Split View, both scenes are
.foregroundActive. Only updating one scene leaves the other with stale orientation constraints.
- App launch timing: During app startup (called from
init()), scenes may still be in .foregroundInactive state before transitioning to .foregroundActive.
Research Findings
How orientation resolution works
The system determines allowed orientations through an intersection:
application(_:supportedInterfaceOrientationsFor:) is called per window (the window parameter identifies which window)
- The root VC's
supportedInterfaceOrientations is queried
- The system intersects both results
setNeedsUpdateOfSupportedInterfaceOrientations() (iOS 16+) operates on a specific VC in a specific window in a specific scene. Each scene's root VC must be notified independently.
Orientation is device-wide, not per-scene
On iPad, all scenes share the same physical screen and rotate together. You cannot have one scene in portrait and another in landscape. Since orientation is a physical device property, the library's app-wide constraint model is fundamentally correct — but all scenes need to be notified.
Scene activation states
| State |
Description |
.foregroundActive |
In foreground, receiving events |
.foregroundInactive |
In foreground, NOT receiving events (e.g., during transitions) |
.background |
In background |
.unattached |
Connected but not yet visible |
In iPad Split View, both scenes are .foregroundActive, so filtering by that state still returns multiple results.
iOS 26 future direction
Apple announced in WWDC 2025 (Make your UIKit app more flexible):
UIRequiresFullScreen is deprecated and will be ignored in future releases
- New per-VC API:
prefersInterfaceOrientationLocked provides Apple's officially recommended orientation locking
- Scene lifecycle adoption is transitioning from encouraged to mandatory
See also TN3192: Migrating from UIRequiresFullScreen.
Proposed Solution
Iterate all connected window scenes instead of picking one:
private func updateSupportedInterfaceOrientations() {
if #available(iOS 16.0, *) {
for case let windowScene as UIWindowScene in UIApplication.shared.connectedScenes {
windowScene.keyWindow?.rootViewController?
.setNeedsUpdateOfSupportedInterfaceOrientations()
}
} else {
UIViewController.attemptRotationToDeviceOrientation()
}
}
This handles:
- App launch: Zero scenes is a no-op loop (no crash, no error log needed)
- iPad Split View: Both scenes get updated
- Single-scene iPhone: The loop runs once, same behavior as before
- Non-determinism: Eliminated — we don't pick one from the Set
Alternative considered: Filter for .foregroundActive with fallback
guard let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
?? UIApplication.shared.connectedScenes.first as? UIWindowScene
else { ... }
This was rejected because it still only updates ONE scene, missing the second scene in iPad Split View.
Future consideration: iOS 26 adoption
When raising the minimum target to iOS 26+, consider adopting prefersInterfaceOrientationLocked as Apple's officially recommended replacement for orientation locking patterns.
References
Problem
InterfaceOrientationManager.updateSupportedInterfaceOrientations()atInterfaceOrientationManager.swift:168-178uses.firstonUIApplication.shared.connectedScenesto find the window scene:This has three issues:
connectedScenesis aSet<UIScene>, meaning.firsthas no guaranteed ordering. It may grab a background scene rather than the active one..foregroundActive. Only updating one scene leaves the other with stale orientation constraints.init()), scenes may still be in.foregroundInactivestate before transitioning to.foregroundActive.Research Findings
How orientation resolution works
The system determines allowed orientations through an intersection:
application(_:supportedInterfaceOrientationsFor:)is called per window (thewindowparameter identifies which window)supportedInterfaceOrientationsis queriedsetNeedsUpdateOfSupportedInterfaceOrientations()(iOS 16+) operates on a specific VC in a specific window in a specific scene. Each scene's root VC must be notified independently.Orientation is device-wide, not per-scene
On iPad, all scenes share the same physical screen and rotate together. You cannot have one scene in portrait and another in landscape. Since orientation is a physical device property, the library's app-wide constraint model is fundamentally correct — but all scenes need to be notified.
Scene activation states
.foregroundActive.foregroundInactive.background.unattachedIn iPad Split View, both scenes are
.foregroundActive, so filtering by that state still returns multiple results.iOS 26 future direction
Apple announced in WWDC 2025 (Make your UIKit app more flexible):
UIRequiresFullScreenis deprecated and will be ignored in future releasesprefersInterfaceOrientationLockedprovides Apple's officially recommended orientation lockingSee also TN3192: Migrating from UIRequiresFullScreen.
Proposed Solution
Iterate all connected window scenes instead of picking one:
This handles:
Alternative considered: Filter for
.foregroundActivewith fallbackThis was rejected because it still only updates ONE scene, missing the second scene in iPad Split View.
Future consideration: iOS 26 adoption
When raising the minimum target to iOS 26+, consider adopting
prefersInterfaceOrientationLockedas Apple's officially recommended replacement for orientation locking patterns.References