diff --git a/Example/ExampleSnapshotTests/_Helper/Snapshotting+WindowedImage.swift b/Example/ExampleSnapshotTests/_Helper/Snapshotting+WindowedImage.swift index cd6dbf2..5dca880 100644 --- a/Example/ExampleSnapshotTests/_Helper/Snapshotting+WindowedImage.swift +++ b/Example/ExampleSnapshotTests/_Helper/Snapshotting+WindowedImage.swift @@ -8,6 +8,8 @@ import SnapshotTesting +@testable import GradientLoadingBar + // MARK: - Config private enum Config { @@ -25,7 +27,7 @@ extension Snapshotting where Value: UIViewController, Format == UIImage { Async { callback in UIView.setAnimationsEnabled(false) - guard let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { + guard let keyWindow = UIApplication.shared.keyWindowInConnectedScenes else { fatalError("⚠️ – Failed to get key window from application!") } keyWindow.rootViewController = viewController diff --git a/Example/ExampleTests/ViewModel/GradientLoadingBarViewModelTestCase.swift b/Example/ExampleTests/ViewModel/GradientLoadingBarViewModelTestCase.swift index 3f73e1f..2b923e0 100644 --- a/Example/ExampleTests/ViewModel/GradientLoadingBarViewModelTestCase.swift +++ b/Example/ExampleTests/ViewModel/GradientLoadingBarViewModelTestCase.swift @@ -47,9 +47,8 @@ final class GradientLoadingBarViewModelTestCase: XCTestCase { func test_initializer_shouldSetupSuperviewObservable_withKeyWindow() { // Given - let keyWindow = KeyWindow() - let passiveWindow = PassiveWindow() - sharedApplicationMock.windows = [keyWindow, passiveWindow] + let keyWindow = UIWindow() + sharedApplicationMock.keyWindowInConnectedScenes = keyWindow // When let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, @@ -61,15 +60,15 @@ final class GradientLoadingBarViewModelTestCase: XCTestCase { func test_initializer_shouldSetupSuperviewObservable_afterUIWindowDidBecomeKeyNotification() { // Given - let keyWindow = KeyWindow() - let passiveWindow = PassiveWindow() - sharedApplicationMock.windows = [passiveWindow] + sharedApplicationMock.keyWindowInConnectedScenes = nil let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, notificationCenter: notificationCenter) // When - sharedApplicationMock.windows.append(keyWindow) + let keyWindow = UIWindow() + sharedApplicationMock.keyWindowInConnectedScenes = keyWindow + notificationCenter.post(name: UIWindow.didBecomeKeyNotification, object: nil) @@ -79,9 +78,8 @@ final class GradientLoadingBarViewModelTestCase: XCTestCase { func test_deinit_shouldResetSuperviewObservable_withNil() { // Given - let keyWindow = KeyWindow() - let passiveWindow = PassiveWindow() - sharedApplicationMock.windows = [keyWindow, passiveWindow] + let keyWindow = UIWindow() + sharedApplicationMock.keyWindowInConnectedScenes = keyWindow var viewModel: GradientLoadingBarViewModel? = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, notificationCenter: notificationCenter) @@ -108,18 +106,8 @@ final class GradientLoadingBarViewModelTestCase: XCTestCase { } } -// MARK: - Helpers - -private final class KeyWindow: UIWindow { - override var isKeyWindow: Bool { true } -} - -private final class PassiveWindow: UIWindow { - override var isKeyWindow: Bool { false } -} - // MARK: - Mocks private final class SharedApplicationMock: UIApplicationProtocol { - var windows = [UIWindow]() + var keyWindowInConnectedScenes: UIWindow? } diff --git a/Example/ExampleTests/Views/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift b/Example/ExampleTests/Views/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift index 30696c4..c289d88 100644 --- a/Example/ExampleTests/Views/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift +++ b/Example/ExampleTests/Views/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift @@ -24,7 +24,7 @@ final class GradientActivityIndicatorViewAnimateIsHiddenTestCase: XCTestCase { // In order for UIView animations to be executed correctly, the corresponding view has to be attached to a visible window. // Therefore we're gonna use the current key-window, add our testing view here in `setUp()` and remove it later in `tearDown()`. - window = UIApplication.shared.windows.first { $0.isKeyWindow } + window = UIApplication.shared.keyWindowInConnectedScenes gradientActivityIndicatorView = GradientActivityIndicatorView() window.addSubview(gradientActivityIndicatorView) diff --git a/GradientLoadingBar/ViewModel/GradientLoadingBarViewModel.swift b/GradientLoadingBar/ViewModel/GradientLoadingBarViewModel.swift index 71c74c2..5d4e740 100644 --- a/GradientLoadingBar/ViewModel/GradientLoadingBarViewModel.swift +++ b/GradientLoadingBar/ViewModel/GradientLoadingBarViewModel.swift @@ -34,7 +34,7 @@ final class GradientLoadingBarViewModel { self.sharedApplication = sharedApplication self.notificationCenter = notificationCenter - if let keyWindow = sharedApplication.windows.first(where: { $0.isKeyWindow }) { + if let keyWindow = sharedApplication.keyWindowInConnectedScenes { superviewSubject.value = keyWindow } @@ -56,7 +56,7 @@ final class GradientLoadingBarViewModel { // MARK: - Private methods @objc private func didReceiveUIWindowDidBecomeKeyNotification(_: Notification) { - guard let keyWindow = sharedApplication.windows.first(where: { $0.isKeyWindow }) else { return } + guard let keyWindow = sharedApplication.keyWindowInConnectedScenes else { return } superviewSubject.value = keyWindow } @@ -66,7 +66,21 @@ final class GradientLoadingBarViewModel { /// This allows mocking `UIApplication` in tests. protocol UIApplicationProtocol: AnyObject { - var windows: [UIWindow] { get } + var keyWindowInConnectedScenes: UIWindow? { get } } -extension UIApplication: UIApplicationProtocol {} +extension UIApplication: UIApplicationProtocol { + /// Returns the current key window across multiple iOS versions. + var keyWindowInConnectedScenes: UIWindow? { + guard #available(iOS 15.0, *) else { + return windows.first { $0.isKeyWindow } + } + + // Starting from iOS 15.0 we need to use `UIWindowScene.windows` on a relevant window scene instead. + // Source: https://stackoverflow.com/a/58031897 + return connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap(\.windows) + .first { $0.isKeyWindow } + } +}