Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multilevel window support for stacked presentation #385

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Source/Infra/EKWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -23,6 +23,7 @@ class EKWindow: UIWindow {
} else {
super.init(frame: UIScreen.main.bounds)
}
windowLevel = level
backgroundColor = .clear
rootViewController = rootVC
accessibilityViewIsModal = true
Expand All @@ -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
}

Expand Down
69 changes: 64 additions & 5 deletions Source/Infra/EKWindowProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand All @@ -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 {
Expand All @@ -64,7 +121,6 @@ final class EKWindowProvider: EntryPresenterDelegate {
}
entryVC.setStatusBarStyle(for: attributes)

entryWindow.windowLevel = attributes.windowLevel.value
if presentInsideKeyWindow {
entryWindow.makeKeyAndVisible()
} else {
Expand All @@ -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!
Expand Down Expand Up @@ -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
}
Expand Down
22 changes: 12 additions & 10 deletions Source/SwiftEntryKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}

/**
Expand All @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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()
}
}
}
Expand Down