From 5f31610489e798e1f968b31497ecf0bb35e0c770 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sun, 4 Oct 2026 23:27:18 +0200 Subject: [PATCH] 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. --- .../UIApplication+ForegroundScene.swift | 30 +++++++++++++++++++ .../Player/TVDisplayModeManager.swift | 7 +---- .../VideoSwipeActionsModifier.swift | 6 ++-- .../Player/ExpandedPlayerSheet+Layouts.swift | 4 +-- .../ExpandedPlayerSheet+Orientation.swift | 7 +---- .../Views/Player/WideScreenPlayerLayout.swift | 10 ++----- 6 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 Yattee/Extensions/UIApplication+ForegroundScene.swift diff --git a/Yattee/Extensions/UIApplication+ForegroundScene.swift b/Yattee/Extensions/UIApplication+ForegroundScene.swift new file mode 100644 index 00000000..bc0672cf --- /dev/null +++ b/Yattee/Extensions/UIApplication+ForegroundScene.swift @@ -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 diff --git a/Yattee/Services/Player/TVDisplayModeManager.swift b/Yattee/Services/Player/TVDisplayModeManager.swift index c3d8511c..206614cf 100644 --- a/Yattee/Services/Player/TVDisplayModeManager.swift +++ b/Yattee/Services/Player/TVDisplayModeManager.swift @@ -146,12 +146,7 @@ final class TVDisplayModeManager { } private func activeDisplayManager() -> AVDisplayManager? { - let scenes = UIApplication.shared.connectedScenes - .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 + UIApplication.shared.foregroundWindow?.avDisplayManager } private func makeFormatDescription(for dynamicRange: TVDisplayDynamicRange) -> CMVideoFormatDescription { diff --git a/Yattee/Views/Components/VideoSwipeActionsModifier.swift b/Yattee/Views/Components/VideoSwipeActionsModifier.swift index 2af22f3c..9cb0545d 100644 --- a/Yattee/Views/Components/VideoSwipeActionsModifier.swift +++ b/Yattee/Views/Components/VideoSwipeActionsModifier.swift @@ -186,9 +186,9 @@ struct VideoSwipeActionsModifier: ViewModifier { activityItems: [appEnvironment?.shareURL(for: video) ?? video.shareURL], applicationActivities: nil ) - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first, - let rootVC = window.rootViewController { + // The key window is the expanded player's while it is up, so the + // share sheet opens above it rather than behind it. + if let rootVC = UIApplication.shared.foregroundWindow?.rootViewController { // Find the top-most view controller var topVC = rootVC while let presented = topVC.presentedViewController { diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift index a757843e..08a42c61 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+Layouts.swift @@ -1767,9 +1767,7 @@ extension ExpandedPlayerSheet { Text(verbatim: "fullH: \(Int(fullHeight)) offset: \(Int(controlsOffset))") Text(verbatim: "pinned: \(isPanelPinned ? "Y" : "N") vis: \(isPanelVisible ? "Y" : "N") side: \(panelSide == .left ? "L" : "R")") #if os(iOS) - let orientation = UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .first?.interfaceOrientation + let orientation = UIApplication.shared.foregroundWindowScene?.interfaceOrientation Text(verbatim: "orient: \(orientation == .landscapeLeft ? "LL" : orientation == .landscapeRight ? "LR" : "P")") #endif } diff --git a/Yattee/Views/Player/ExpandedPlayerSheet+Orientation.swift b/Yattee/Views/Player/ExpandedPlayerSheet+Orientation.swift index 7ca635c2..c925d8f2 100644 --- a/Yattee/Views/Player/ExpandedPlayerSheet+Orientation.swift +++ b/Yattee/Views/Player/ExpandedPlayerSheet+Orientation.swift @@ -15,12 +15,7 @@ extension ExpandedPlayerSheet { /// Get safe area insets from the window. var windowSafeAreaInsets: UIEdgeInsets { - UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .first? - .windows - .first? - .safeAreaInsets ?? .zero + UIApplication.shared.foregroundWindow?.safeAreaInsets ?? .zero } // MARK: - Orientation Lock diff --git a/Yattee/Views/Player/WideScreenPlayerLayout.swift b/Yattee/Views/Player/WideScreenPlayerLayout.swift index 7de32af5..9a295b91 100644 --- a/Yattee/Views/Player/WideScreenPlayerLayout.swift +++ b/Yattee/Views/Player/WideScreenPlayerLayout.swift @@ -118,11 +118,7 @@ struct WideScreenPlayerLayout: View { /// requiring `.foregroundActive` would drop the safe area to zero while /// they are open and make the panel jump to the screen edge. private var foregroundWindow: UIWindow? { - let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } - let scene = scenes.first { $0.activationState == .foregroundActive } - ?? scenes.first { $0.activationState == .foregroundInactive } - ?? scenes.first - return scene?.windows.first { $0.isKeyWindow } ?? scene?.windows.first + UIApplication.shared.foregroundWindow } #endif @@ -187,9 +183,7 @@ struct WideScreenPlayerLayout: View { // deployment target allows, and consider `traitCollection.verticalBarEdge` (iOS 27.1) // for the bar-edge question. Global scene lookup should be replaced by the hosting // window (e.g. via a UIViewRepresentable reading `view.window?.windowScene`). - let interfaceOrientation = UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .first? + let interfaceOrientation = UIApplication.shared.foregroundWindowScene? .interfaceOrientation ?? .unknown // landscapeLeft = home/gesture bar on left = Dynamic Island on RIGHT // landscapeRight = home/gesture bar on right = Dynamic Island on LEFT