diff --git a/Source/Infra/EKWindow.swift b/Source/Infra/EKWindow.swift index d7cd128..a3523c0 100644 --- a/Source/Infra/EKWindow.swift +++ b/Source/Infra/EKWindow.swift @@ -12,7 +12,7 @@ class EKWindow: UIWindow { var isAbleToReceiveTouches = false - init(with rootVC: UIViewController) { + init(with rootVC: UIViewController, level: UIWindow.Level) { if #available(iOS 13.0, *) { // TODO: Patched to support SwiftUI out of the box but should require attendance if let scene = UIApplication.shared.connectedScenes.filter({$0.activationState == .foregroundActive}).first as? UIWindowScene { @@ -23,6 +23,7 @@ class EKWindow: UIWindow { } else { super.init(frame: UIScreen.main.bounds) } + windowLevel = level backgroundColor = .clear rootViewController = rootVC accessibilityViewIsModal = true @@ -37,7 +38,7 @@ class EKWindow: UIWindow { return super.hitTest(point, with: event) } - guard let rootVC = EKWindowProvider.shared.rootVC else { + guard let rootVC = EKWindowProvider.provider(for: windowLevel).rootVC else { return nil } diff --git a/Source/Infra/EKWindowProvider.swift b/Source/Infra/EKWindowProvider.swift index 5cd4e00..271badd 100644 --- a/Source/Infra/EKWindowProvider.swift +++ b/Source/Infra/EKWindowProvider.swift @@ -13,15 +13,68 @@ final class EKWindowProvider: EntryPresenterDelegate { /** The artificial safe area insets */ static var safeAreaInsets: UIEdgeInsets { if #available(iOS 11.0, *) { - return EKWindowProvider.shared.entryWindow?.rootViewController?.view?.safeAreaInsets ?? UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets ?? .zero + return EKWindowProvider.provider(for: .normal).entryWindow?.rootViewController?.view?.safeAreaInsets ?? UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets ?? .zero } else { let statusBarMaxY = UIApplication.shared.statusBarFrame.maxY return UIEdgeInsets(top: statusBarMaxY, left: 0, bottom: 10, right: 0) } } + private static var providers: [UIWindow.Level: EKWindowProvider] = [:] /** Single access point */ - static let shared = EKWindowProvider() + static func provider(for level: UIWindow.Level) -> EKWindowProvider { + if let provider = providers[level] { + return provider + } else { + let provider = EKWindowProvider(level: level) + providers[level] = provider + return provider + } + } + + static var topMostProvider: EKWindowProvider? { + if let topMostLevel = providers.keys.sorted(by: { $0 > $1 }).first { + return provider(for: topMostLevel) + } + return nil + } + + static func isCurrentlyDisplaying(entryNamed name: String? = nil) -> Bool { + for provider in providers.values + where provider.isCurrentlyDisplaying(entryNamed: name) { + return true + } + return false + } + + static func queueContains(entryNamed name: String? = nil) -> Bool { + for provider in providers.values + where provider.queueContains(entryNamed: name) { + return true + } + return false + } + + static func dismiss(_ descriptor: SwiftEntryKit.EntryDismissalDescriptor, with completion: SwiftEntryKit.DismissCompletionHandler? = nil) { + + switch descriptor { + case .displayed: + topMostProvider?.rootVC?.animateOutLastEntry(completionHandler: completion) + default: + let dispatchGroup = DispatchGroup() + for provider in providers.values { + dispatchGroup.enter() + provider.dismiss(descriptor) { + dispatchGroup.leave() + } + } + dispatchGroup.notify(queue: .main, execute: completion ?? {}) + } + } + + static func layoutIfNeeded() { + providers.values.forEach { $0.layoutIfNeeded() } + } /** Current entry window */ var entryWindow: EKWindow! @@ -41,9 +94,13 @@ final class EKWindowProvider: EntryPresenterDelegate { private let entryQueue = EKAttributes.Precedence.QueueingHeuristic.value.heuristic private weak var entryView: EKEntryView! + + private let windowLevel: UIWindow.Level /** Cannot be instantiated, customized, inherited */ - private init() {} + private init(level: UIWindow.Level) { + windowLevel = level + } var isResponsiveToTouches: Bool { set { @@ -64,7 +121,6 @@ final class EKWindowProvider: EntryPresenterDelegate { } entryVC.setStatusBarStyle(for: attributes) - entryWindow.windowLevel = attributes.windowLevel.value if presentInsideKeyWindow { entryWindow.makeKeyAndVisible() } else { @@ -79,7 +135,7 @@ final class EKWindowProvider: EntryPresenterDelegate { let entryVC: EKRootViewController if entryWindow == nil { entryVC = EKRootViewController(with: self) - entryWindow = EKWindow(with: entryVC) + entryWindow = EKWindow(with: entryVC, level: windowLevel) mainRollbackWindow = UIApplication.shared.keyWindow } else { entryVC = rootVC! @@ -151,6 +207,9 @@ final class EKWindowProvider: EntryPresenterDelegate { /** Clear all entries immediately and display to the rollback window */ func displayRollbackWindow() { + defer { + EKWindowProvider.providers.removeValue(forKey: windowLevel) + } if #available(iOS 13.0, *) { entryWindow.windowScene = nil } diff --git a/Source/SwiftEntryKit.swift b/Source/SwiftEntryKit.swift index 4d57291..c6b128f 100644 --- a/Source/SwiftEntryKit.swift +++ b/Source/SwiftEntryKit.swift @@ -53,8 +53,8 @@ public final class SwiftEntryKit { no entry is currently displayed. This can be used */ - public class var window: UIWindow? { - return EKWindowProvider.shared.entryWindow + public class func window(for level: UIWindow.Level) -> UIWindow? { + return EKWindowProvider.provider(for: level).entryWindow } /** @@ -74,7 +74,7 @@ public final class SwiftEntryKit { - parameter name: The name of the entry. Its default value is *nil*. */ public class func isCurrentlyDisplaying(entryNamed name: String? = nil) -> Bool { - return EKWindowProvider.shared.isCurrentlyDisplaying(entryNamed: name) + return EKWindowProvider.isCurrentlyDisplaying(entryNamed: name) } /** @@ -93,7 +93,7 @@ public final class SwiftEntryKit { - parameter name: The name of the entry. Its default value is *nil*. */ public class func queueContains(entryNamed name: String? = nil) -> Bool { - return EKWindowProvider.shared.queueContains(entryNamed: name) + return EKWindowProvider.queueContains(entryNamed: name) } /** @@ -107,7 +107,8 @@ public final class SwiftEntryKit { */ public class func display(entry view: UIView, using attributes: EKAttributes, presentInsideKeyWindow: Bool = false, rollbackWindow: RollbackWindow = .main) { DispatchQueue.main.async { - EKWindowProvider.shared.display(view: view, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow) + EKWindowProvider.provider(for: attributes.windowLevel.value) + .display(view: view, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow) } } @@ -122,7 +123,8 @@ public final class SwiftEntryKit { */ public class func display(entry viewController: UIViewController, using attributes: EKAttributes, presentInsideKeyWindow: Bool = false, rollbackWindow: RollbackWindow = .main) { DispatchQueue.main.async { - EKWindowProvider.shared.display(viewController: viewController, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow) + EKWindowProvider.provider(for: attributes.windowLevel.value) + .display(viewController: viewController, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow) } } @@ -135,7 +137,7 @@ public final class SwiftEntryKit { */ public class func transform(to view: UIView) { DispatchQueue.main.async { - EKWindowProvider.shared.transform(to: view) + EKWindowProvider.topMostProvider?.transform(to: view) } } @@ -148,7 +150,7 @@ public final class SwiftEntryKit { */ public class func dismiss(_ descriptor: EntryDismissalDescriptor = .displayed, with completion: DismissCompletionHandler? = nil) { DispatchQueue.main.async { - EKWindowProvider.shared.dismiss(descriptor, with: completion) + EKWindowProvider.dismiss(descriptor, with: completion) } } @@ -160,10 +162,10 @@ public final class SwiftEntryKit { */ public class func layoutIfNeeded() { if Thread.isMainThread { - EKWindowProvider.shared.layoutIfNeeded() + EKWindowProvider.layoutIfNeeded() } else { DispatchQueue.main.async { - EKWindowProvider.shared.layoutIfNeeded() + EKWindowProvider.layoutIfNeeded() } } }