Look up the foreground scene instead of the first connected one

The swipe-action share sheet, the expanded player's safe area and the
Dynamic Island side check took connectedScenes.first, which ignores
which scene is active (on the iPhone Duo it can be the other panel's)
and presented the share sheet from the main window, behind an expanded
player.

UIApplication.foregroundWindowScene/foregroundWindow resolve the
foreground-active scene (else foreground-inactive, else any) and its
key window, the order the widescreen layout already used. The share
sheet now presents from the key window, which is the player's while it
is up. The tvOS display mode manager uses the same lookup.
This commit is contained in:
Arkadiusz Fal
2026-10-04 23:27:18 +02:00
parent 1c7314f74a
commit 5f31610489
6 changed files with 38 additions and 26 deletions

View File

@@ -0,0 +1,30 @@
//
// UIApplication+ForegroundScene.swift
// Yattee
//
// The window scene and window the user is looking at, for code that has no
// view of its own to ask. `connectedScenes.first` is not that: on the iPhone
// Duo it can be the other panel's scene, and it ignores which one is active.
//
#if canImport(UIKit)
import UIKit
extension UIApplication {
/// The foreground-active scene, else a foreground-inactive one (Control
/// Center or Notification Center open over the app), else any scene.
var foregroundWindowScene: UIWindowScene? {
let scenes = connectedScenes.compactMap { $0 as? UIWindowScene }
return scenes.first { $0.activationState == .foregroundActive }
?? scenes.first { $0.activationState == .foregroundInactive }
?? scenes.first
}
/// The key window of `foregroundWindowScene` (the expanded player's window
/// while it is up), else its first window.
var foregroundWindow: UIWindow? {
let scene = foregroundWindowScene
return scene?.windows.first { $0.isKeyWindow } ?? scene?.windows.first
}
}
#endif

View File

@@ -146,12 +146,7 @@ final class TVDisplayModeManager {
} }
private func activeDisplayManager() -> AVDisplayManager? { private func activeDisplayManager() -> AVDisplayManager? {
let scenes = UIApplication.shared.connectedScenes UIApplication.shared.foregroundWindow?.avDisplayManager
.compactMap { $0 as? UIWindowScene }
let scene = scenes.first { $0.activationState == .foregroundActive }
?? scenes.first
let window = scene?.windows.first { $0.isKeyWindow } ?? scene?.windows.first
return window?.avDisplayManager
} }
private func makeFormatDescription(for dynamicRange: TVDisplayDynamicRange) -> CMVideoFormatDescription { private func makeFormatDescription(for dynamicRange: TVDisplayDynamicRange) -> CMVideoFormatDescription {

View File

@@ -186,9 +186,9 @@ struct VideoSwipeActionsModifier: ViewModifier {
activityItems: [appEnvironment?.shareURL(for: video) ?? video.shareURL], activityItems: [appEnvironment?.shareURL(for: video) ?? video.shareURL],
applicationActivities: nil applicationActivities: nil
) )
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, // The key window is the expanded player's while it is up, so the
let window = windowScene.windows.first, // share sheet opens above it rather than behind it.
let rootVC = window.rootViewController { if let rootVC = UIApplication.shared.foregroundWindow?.rootViewController {
// Find the top-most view controller // Find the top-most view controller
var topVC = rootVC var topVC = rootVC
while let presented = topVC.presentedViewController { while let presented = topVC.presentedViewController {

View File

@@ -1767,9 +1767,7 @@ extension ExpandedPlayerSheet {
Text(verbatim: "fullH: \(Int(fullHeight)) offset: \(Int(controlsOffset))") Text(verbatim: "fullH: \(Int(fullHeight)) offset: \(Int(controlsOffset))")
Text(verbatim: "pinned: \(isPanelPinned ? "Y" : "N") vis: \(isPanelVisible ? "Y" : "N") side: \(panelSide == .left ? "L" : "R")") Text(verbatim: "pinned: \(isPanelPinned ? "Y" : "N") vis: \(isPanelVisible ? "Y" : "N") side: \(panelSide == .left ? "L" : "R")")
#if os(iOS) #if os(iOS)
let orientation = UIApplication.shared.connectedScenes let orientation = UIApplication.shared.foregroundWindowScene?.interfaceOrientation
.compactMap { $0 as? UIWindowScene }
.first?.interfaceOrientation
Text(verbatim: "orient: \(orientation == .landscapeLeft ? "LL" : orientation == .landscapeRight ? "LR" : "P")") Text(verbatim: "orient: \(orientation == .landscapeLeft ? "LL" : orientation == .landscapeRight ? "LR" : "P")")
#endif #endif
} }

View File

@@ -15,12 +15,7 @@ extension ExpandedPlayerSheet {
/// Get safe area insets from the window. /// Get safe area insets from the window.
var windowSafeAreaInsets: UIEdgeInsets { var windowSafeAreaInsets: UIEdgeInsets {
UIApplication.shared.connectedScenes UIApplication.shared.foregroundWindow?.safeAreaInsets ?? .zero
.compactMap { $0 as? UIWindowScene }
.first?
.windows
.first?
.safeAreaInsets ?? .zero
} }
// MARK: - Orientation Lock // MARK: - Orientation Lock

View File

@@ -118,11 +118,7 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
/// requiring `.foregroundActive` would drop the safe area to zero while /// requiring `.foregroundActive` would drop the safe area to zero while
/// they are open and make the panel jump to the screen edge. /// they are open and make the panel jump to the screen edge.
private var foregroundWindow: UIWindow? { private var foregroundWindow: UIWindow? {
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } UIApplication.shared.foregroundWindow
let scene = scenes.first { $0.activationState == .foregroundActive }
?? scenes.first { $0.activationState == .foregroundInactive }
?? scenes.first
return scene?.windows.first { $0.isKeyWindow } ?? scene?.windows.first
} }
#endif #endif
@@ -187,9 +183,7 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
// deployment target allows, and consider `traitCollection.verticalBarEdge` (iOS 27.1) // deployment target allows, and consider `traitCollection.verticalBarEdge` (iOS 27.1)
// for the bar-edge question. Global scene lookup should be replaced by the hosting // for the bar-edge question. Global scene lookup should be replaced by the hosting
// window (e.g. via a UIViewRepresentable reading `view.window?.windowScene`). // window (e.g. via a UIViewRepresentable reading `view.window?.windowScene`).
let interfaceOrientation = UIApplication.shared.connectedScenes let interfaceOrientation = UIApplication.shared.foregroundWindowScene?
.compactMap { $0 as? UIWindowScene }
.first?
.interfaceOrientation ?? .unknown .interfaceOrientation ?? .unknown
// landscapeLeft = home/gesture bar on left = Dynamic Island on RIGHT // landscapeLeft = home/gesture bar on left = Dynamic Island on RIGHT
// landscapeRight = home/gesture bar on right = Dynamic Island on LEFT // landscapeRight = home/gesture bar on right = Dynamic Island on LEFT