Skip to content

Commit ddd4d01

Browse files
authored
MOB-11622: Improve multi-window support (#926)
1 parent b6e4d54 commit ddd4d01

File tree

3 files changed

+58
-81
lines changed

3 files changed

+58
-81
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
.DS_Store
22
xcuserdata
33

4-
# AI assistant configuration
54
.claude/
65

76
.swiftpm/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[![pod](https://img.shields.io/cocoapods/v/Iterable-iOS-SDK)](https://cocoapods.org/pods/Iterable-iOS-SDK)
77
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
88

9+
910
# Iterable's iOS SDK
1011

1112
[Iterable](https://www.iterable.com) is a growth marketing platform that helps

swift-sdk/Internal/Utilities/IterableUtil.swift

Lines changed: 57 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -25,39 +25,85 @@ import UIKit
2525

2626
@available(iOS 13.0, *)
2727
private static func getActiveWindowRootViewController() -> UIViewController? {
28-
guard let application = AppExtensionHelper.application else { return nil }
28+
guard let application = AppExtensionHelper.application else {
29+
ITBDebug("No application found in AppExtensionHelper")
30+
return nil
31+
}
32+
33+
ITBDebug("Application has \(application.connectedScenes.count) connected scenes")
2934

3035
// Find active scenes (foreground active takes priority)
3136
let activeScenes = application.connectedScenes
3237
.compactMap { $0 as? UIWindowScene }
3338
.filter { $0.activationState == .foregroundActive }
3439

35-
// Look for key window in active scenes first
36-
for scene in activeScenes {
37-
if let keyWindow = scene.windows.first(where: { $0.isKeyWindow }),
38-
let rootVC = keyWindow.rootViewController {
39-
return rootVC
40+
ITBDebug("Found \(activeScenes.count) foreground active scenes")
41+
42+
// iOS 15+: Use scene's keyWindow property (preferred approach)
43+
if #available(iOS 15.0, *) {
44+
for (index, scene) in activeScenes.enumerated() {
45+
ITBDebug("Checking scene \(index): keyWindow exists = \(scene.keyWindow != nil)")
46+
if let keyWindow = scene.keyWindow,
47+
let rootVC = keyWindow.rootViewController {
48+
ITBDebug("Found root view controller in keyWindow: \(String(describing: rootVC))")
49+
return rootVC
50+
} else {
51+
ITBDebug("Scene \(index): keyWindow or rootVC is nil")
52+
}
53+
}
54+
} else {
55+
// iOS 13-14: Fall back to isKeyWindow check
56+
for (index, scene) in activeScenes.enumerated() {
57+
let keyWindows = scene.windows.filter { $0.isKeyWindow }
58+
ITBDebug("Scene \(index): found \(keyWindows.count) key windows")
59+
if let keyWindow = scene.windows.first(where: { $0.isKeyWindow }),
60+
let rootVC = keyWindow.rootViewController {
61+
ITBDebug("Found root view controller in keyWindow (iOS 13-14): \(String(describing: rootVC))")
62+
return rootVC
63+
} else {
64+
ITBDebug("Scene \(index): no keyWindow with rootVC found")
65+
}
4066
}
4167
}
4268

4369
// Fallback to first window in first active scene
4470
if let firstActiveScene = activeScenes.first,
4571
let rootVC = firstActiveScene.windows.first?.rootViewController {
72+
ITBDebug("Fallback: Found root view controller in first window of first active scene: \(String(describing: rootVC))")
4673
return rootVC
74+
} else {
75+
ITBDebug("Fallback: No root view controller found in first window of first active scene")
4776
}
4877

49-
// Final fallback: any foreground inactive scene with key window
78+
// Final fallback: any foreground inactive scene
5079
let inactiveScenes = application.connectedScenes
5180
.compactMap { $0 as? UIWindowScene }
5281
.filter { $0.activationState == .foregroundInactive }
5382

54-
for scene in inactiveScenes {
55-
if let keyWindow = scene.windows.first(where: { $0.isKeyWindow }),
56-
let rootVC = keyWindow.rootViewController {
57-
return rootVC
83+
ITBDebug("Checking \(inactiveScenes.count) foreground inactive scenes as final fallback")
84+
85+
if #available(iOS 15.0, *) {
86+
for (index, scene) in inactiveScenes.enumerated() {
87+
ITBDebug("Inactive scene \(index): keyWindow exists = \(scene.keyWindow != nil)")
88+
if let keyWindow = scene.keyWindow,
89+
let rootVC = keyWindow.rootViewController {
90+
ITBDebug("Final fallback: Found root view controller in inactive scene keyWindow: \(String(describing: rootVC))")
91+
return rootVC
92+
}
93+
}
94+
} else {
95+
for (index, scene) in inactiveScenes.enumerated() {
96+
let keyWindows = scene.windows.filter { $0.isKeyWindow }
97+
ITBDebug("Inactive scene \(index): found \(keyWindows.count) key windows")
98+
if let keyWindow = scene.windows.first(where: { $0.isKeyWindow }),
99+
let rootVC = keyWindow.rootViewController {
100+
ITBDebug("Final fallback: Found root view controller in inactive scene keyWindow (iOS 13-14): \(String(describing: rootVC))")
101+
return rootVC
102+
}
58103
}
59104
}
60105

106+
ITBDebug("No root view controller found in any scene")
61107
return nil
62108
}
63109

@@ -145,73 +191,4 @@ import UIKit
145191
return true
146192
}
147193
}
148-
149-
@available(iOS 13.0, *)
150-
private static func getActiveWindowRootViewController() -> UIViewController? {
151-
guard let application = AppExtensionHelper.application else { return nil }
152-
153-
// Prioritize foregroundActive scenes for Stage Manager and multi-window scenarios
154-
for scene in application.connectedScenes {
155-
guard let windowScene = scene as? UIWindowScene,
156-
windowScene.activationState == .foregroundActive else { continue }
157-
158-
// Filter for visible, normal-level windows with valid rootViewControllers
159-
let visibleWindows = windowScene.windows.filter {
160-
$0.isHidden == false &&
161-
$0.windowLevel == .normal &&
162-
$0.rootViewController != nil
163-
}
164-
165-
// Try keyWindow first if available (iOS 15+) and in our visible set
166-
if #available(iOS 15.0, *) {
167-
if let keyWindow = windowScene.keyWindow,
168-
visibleWindows.contains(keyWindow) {
169-
return keyWindow.rootViewController
170-
}
171-
}
172-
173-
// For iOS 13-14, find keyWindow manually in visible windows
174-
for window in visibleWindows {
175-
if window.isKeyWindow {
176-
return window.rootViewController
177-
}
178-
}
179-
180-
// Fallback to first visible, normal window
181-
if let firstVisibleWindow = visibleWindows.first {
182-
return firstVisibleWindow.rootViewController
183-
}
184-
}
185-
186-
// Secondary fallback: any foregroundInactive scene (Stage Manager background)
187-
for scene in application.connectedScenes {
188-
guard let windowScene = scene as? UIWindowScene,
189-
windowScene.activationState == .foregroundInactive else { continue }
190-
191-
let visibleWindows = windowScene.windows.filter {
192-
$0.isHidden == false &&
193-
$0.windowLevel == .normal &&
194-
$0.rootViewController != nil
195-
}
196-
197-
if #available(iOS 15.0, *) {
198-
if let keyWindow = windowScene.keyWindow,
199-
visibleWindows.contains(keyWindow) {
200-
return keyWindow.rootViewController
201-
}
202-
}
203-
204-
for window in visibleWindows {
205-
if window.isKeyWindow {
206-
return window.rootViewController
207-
}
208-
}
209-
210-
if let firstVisibleWindow = visibleWindows.first {
211-
return firstVisibleWindow.rootViewController
212-
}
213-
}
214-
215-
return nil
216-
}
217194
}

0 commit comments

Comments
 (0)