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

Support for multiple SwiftEntryKit instances working in parallel #366

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion Source/Infra/EKContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ protocol EntryContentViewDelegate: AnyObject {
func changeToActive(withAttributes attributes: EKAttributes)
func changeToInactive(withAttributes attributes: EKAttributes, pushOut: Bool)
func didFinishDisplaying(entry: EKEntryView, keepWindowActive: Bool, dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?)
var safeAreaInsets: UIEdgeInsets { get }
}

class EKContentView: UIView {
Expand Down Expand Up @@ -119,7 +120,7 @@ class EKContentView: UIView {

// Define a spacer to catch top / bottom offsets
var spacerView: UIView!
let safeAreaInsets = EKWindowProvider.safeAreaInsets
let safeAreaInsets = entryDelegate.safeAreaInsets
let overrideSafeArea = attributes.positionConstraints.safeArea.isOverridden

if !overrideSafeArea && safeAreaInsets.hasVerticalInsets && !attributes.position.isCenter {
Expand Down
13 changes: 10 additions & 3 deletions Source/Infra/EKEntryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import UIKit

protocol EntryViewDelegate: AnyObject {
var safeAreaInsets: UIEdgeInsets { get }
}

class EKEntryView: EKStyleView {

struct Content {
Expand All @@ -28,6 +32,8 @@ class EKEntryView: EKStyleView {
}

// MARK: Props

private weak var delegate: EntryViewDelegate!

/** Background view */
private var backgroundView: EKBackgroundView!
Expand All @@ -53,8 +59,9 @@ class EKEntryView: EKStyleView {
}()

// MARK: Setup
init(newEntry content: Content) {
init(newEntry content: Content, delegate: EntryViewDelegate) {
self.content = content
self.delegate = delegate
super.init(frame: UIScreen.main.bounds)
setupContentView()
applyDropShadow()
Expand Down Expand Up @@ -161,9 +168,9 @@ class EKEntryView: EKStyleView {
var bottomInset: CGFloat = 0
switch attributes.position {
case .top:
topInset = -EKWindowProvider.safeAreaInsets.top
topInset = -delegate.safeAreaInsets.top
case .bottom, .center:
bottomInset = EKWindowProvider.safeAreaInsets.bottom
bottomInset = delegate.safeAreaInsets.bottom
}

backgroundView.layoutToSuperview(.top, offset: topInset)
Expand Down
6 changes: 5 additions & 1 deletion Source/Infra/EKRootViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import UIKit
protocol EntryPresenterDelegate: AnyObject {
var isResponsiveToTouches: Bool { set get }
func displayPendingEntryOrRollbackWindow(dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?)
var safeAreaInsets: UIEdgeInsets { get }
}

class EKRootViewController: UIViewController {
Expand Down Expand Up @@ -213,7 +214,10 @@ extension EKRootViewController {
// MARK: - EntryScrollViewDelegate

extension EKRootViewController: EntryContentViewDelegate {

var safeAreaInsets: UIEdgeInsets {
delegate.safeAreaInsets
}

func didFinishDisplaying(entry: EKEntryView, keepWindowActive: Bool, dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?) {
guard !isDisplaying else {
return
Expand Down
4 changes: 2 additions & 2 deletions Source/Infra/EKWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit

class EKWindow: UIWindow {

var isAbleToReceiveTouches = false

init(with rootVC: UIViewController) {
Expand Down Expand Up @@ -37,7 +37,7 @@ class EKWindow: UIWindow {
return super.hitTest(point, with: event)
}

guard let rootVC = EKWindowProvider.shared.rootVC else {
guard let rootVC = rootViewController as? EKRootViewController else {
return nil
}

Expand Down
19 changes: 8 additions & 11 deletions Source/Infra/EKWindowProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,18 @@

import UIKit

final class EKWindowProvider: EntryPresenterDelegate {
final class EKWindowProvider: EntryPresenterDelegate, EntryViewDelegate {

/** The artificial safe area insets */
static var safeAreaInsets: UIEdgeInsets {
var safeAreaInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return EKWindowProvider.shared.entryWindow?.rootViewController?.view?.safeAreaInsets ?? UIApplication.shared.keyWindow?.rootViewController?.view.safeAreaInsets ?? .zero
return 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)
}
}

/** Single access point */
static let shared = EKWindowProvider()


/** Current entry window */
var entryWindow: EKWindow!

Expand All @@ -43,7 +40,7 @@ final class EKWindowProvider: EntryPresenterDelegate {
private weak var entryView: EKEntryView!

/** Cannot be instantiated, customized, inherited */
private init() {}
init() {}

var isResponsiveToTouches: Bool {
set {
Expand Down Expand Up @@ -139,13 +136,13 @@ final class EKWindowProvider: EntryPresenterDelegate {

/** Display a view using attributes */
func display(view: UIView, using attributes: EKAttributes, presentInsideKeyWindow: Bool, rollbackWindow: SwiftEntryKit.RollbackWindow) {
let entryView = EKEntryView(newEntry: .init(view: view, attributes: attributes))
let entryView = EKEntryView(newEntry: .init(view: view, attributes: attributes), delegate: self)
display(entryView: entryView, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}

/** Display a view controller using attributes */
func display(viewController: UIViewController, using attributes: EKAttributes, presentInsideKeyWindow: Bool, rollbackWindow: SwiftEntryKit.RollbackWindow) {
let entryView = EKEntryView(newEntry: .init(viewController: viewController, attributes: attributes))
let entryView = EKEntryView(newEntry: .init(viewController: viewController, attributes: attributes), delegate: self)
display(entryView: entryView, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}

Expand Down
151 changes: 131 additions & 20 deletions Source/SwiftEntryKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,41 @@ public final class SwiftEntryKit {

/** Completion handler for the dismissal method */
public typealias DismissCompletionHandler = () -> Void

/// Shared instance, used by class functions
public static let shared = SwiftEntryKit()

let windowProvider = EKWindowProvider()

/** Cannot be instantiated, customized, inherited. */
private init() {}

public init() {}

/**
Returns the window that displays the entry.
**Warning**: the returned `UIWindow` instance is `nil` in case
no entry is currently displayed.
This can be used
*/
public var window: UIWindow? {
return windowProvider.entryWindow
}

/**
Returns the window that displays the entry.
**Warning**: the returned `UIWindow` instance is `nil` in case
no entry is currently displayed.
This can be used
*/
public class var window: UIWindow? {
return EKWindowProvider.shared.entryWindow
return shared.window
}

/**
Returns true if **any** entry is currently displayed.
- Not thread safe - should be called from the main queue only in order to receive a reliable result.
- Convenience computed variable. Using it is the same as invoking **isCurrentlyDisplaying() -> Bool** (witohut the name of the entry).
*/
public var isCurrentlyDisplaying: Bool {
return isCurrentlyDisplaying()
}

/**
Expand All @@ -63,7 +86,18 @@ public final class SwiftEntryKit {
- Convenience computed variable. Using it is the same as invoking **isCurrentlyDisplaying() -> Bool** (witohut the name of the entry).
*/
public class var isCurrentlyDisplaying: Bool {
return isCurrentlyDisplaying()
return Self.isCurrentlyDisplaying()
}

/**
Returns true if an entry with a given name is currently displayed.
- Not thread safe - should be called from the main queue only in order to receive a reliable result.
- If invoked with *name* = *nil* or without the parameter value, it will return *true* if **any** entry is currently displayed.
- Returns a *false* value for currently enqueued entries.
- parameter name: The name of the entry. Its default value is *nil*.
*/
public func isCurrentlyDisplaying(entryNamed name: String? = nil) -> Bool {
return windowProvider.isCurrentlyDisplaying(entryNamed: name)
}

/**
Expand All @@ -74,9 +108,18 @@ 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 shared.isCurrentlyDisplaying(entryNamed: name)
}


/**
Returns true if **any** entry is currently enqueued and waiting to be displayed.
- Not thread safe - should be called from the main queue only in order to receive a reliable result.
- Convenience computed variable. Using it is the same as invoking **~queueContains() -> Bool** (witohut the name of the entry)
*/
public var isQueueEmpty: Bool {
return !queueContains()
}

/**
Returns true if **any** entry is currently enqueued and waiting to be displayed.
- Not thread safe - should be called from the main queue only in order to receive a reliable result.
Expand All @@ -85,15 +128,40 @@ public final class SwiftEntryKit {
public class var isQueueEmpty: Bool {
return !queueContains()
}


/**
Returns true if an entry with a given name is currently enqueued and waiting to be displayed.
- Not thread safe - should be called from the main queue only in order to receive a reliable result.
- If invoked with *name* = *nil* or without the parameter value, it will return *true* if **any** entry is currently displayed, meaning, the queue is not currently empty.
- parameter name: The name of the entry. Its default value is *nil*.
*/
public func queueContains(entryNamed name: String? = nil) -> Bool {
return windowProvider.queueContains(entryNamed: name)
}

/**
Returns true if an entry with a given name is currently enqueued and waiting to be displayed.
- Not thread safe - should be called from the main queue only in order to receive a reliable result.
- If invoked with *name* = *nil* or without the parameter value, it will return *true* if **any** entry is currently displayed, meaning, the queue is not currently empty.
- 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 shared.queueContains(entryNamed: name)
}

/**
Displays a given entry view using an attributes struct.
- A thread-safe method - Can be invokes from any thread
- A class method - Should be called on the class
- parameter view: Custom view that is to be displayed
- parameter attributes: Display properties
- parameter presentInsideKeyWindow: Indicates whether the entry window should become the key window.
- parameter rollbackWindow: After the entry has been dismissed, SwiftEntryKit rolls back to the given window. By default it is *.main* which is the app main window
*/
public func display(entry view: UIView, using attributes: EKAttributes, presentInsideKeyWindow: Bool = false, rollbackWindow: RollbackWindow = .main) {
DispatchQueue.main.async {
self.windowProvider.display(view: view, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}
}

/**
Expand All @@ -106,11 +174,24 @@ public final class SwiftEntryKit {
- parameter rollbackWindow: After the entry has been dismissed, SwiftEntryKit rolls back to the given window. By default it is *.main* which is the app main window
*/
public class func display(entry view: UIView, using attributes: EKAttributes, presentInsideKeyWindow: Bool = false, rollbackWindow: RollbackWindow = .main) {
shared.display(entry: view, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}

/**
Displays a given entry view controller using an attributes struct.
- A thread-safe method - Can be invokes from any thread
- A class method - Should be called on the class
- parameter view: Custom view that is to be displayed
- parameter attributes: Display properties
- parameter presentInsideKeyWindow: Indicates whether the entry window should become the key window.
- parameter rollbackWindow: After the entry has been dismissed, SwiftEntryKit rolls back to the given window. By default it is *.main* - which is the app main window
*/
public func display(entry viewController: UIViewController, using attributes: EKAttributes, presentInsideKeyWindow: Bool = false, rollbackWindow: RollbackWindow = .main) {
DispatchQueue.main.async {
EKWindowProvider.shared.display(view: view, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
self.windowProvider.display(viewController: viewController, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}
}

/**
Displays a given entry view controller using an attributes struct.
- A thread-safe method - Can be invokes from any thread
Expand All @@ -121,8 +202,19 @@ public final class SwiftEntryKit {
- parameter rollbackWindow: After the entry has been dismissed, SwiftEntryKit rolls back to the given window. By default it is *.main* - which is the app main window
*/
public class func display(entry viewController: UIViewController, using attributes: EKAttributes, presentInsideKeyWindow: Bool = false, rollbackWindow: RollbackWindow = .main) {
shared.display(entry: viewController, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
}

/**
ALPHA FEATURE: Transform the previous entry to the current one using the previous attributes struct.
- A thread-safe method - Can be invoked from any thread.
- A class method - Should be called on the class.
- This feature hasn't been fully tested. Use with caution.
- parameter view: Custom view that is to be displayed instead of the currently displayed entry
*/
public func transform(to view: UIView) {
DispatchQueue.main.async {
EKWindowProvider.shared.display(viewController: viewController, using: attributes, presentInsideKeyWindow: presentInsideKeyWindow, rollbackWindow: rollbackWindow)
self.windowProvider.transform(to: view)
}
}

Expand All @@ -134,8 +226,19 @@ public final class SwiftEntryKit {
- parameter view: Custom view that is to be displayed instead of the currently displayed entry
*/
public class func transform(to view: UIView) {
shared.transform(to: view)
}

/**
Dismisses the currently presented entry and removes the presented window instance after the exit animation is concluded.
- A thread-safe method - Can be invoked from any thread.
- A class method - Should be called on the class.
- parameter descriptor: A descriptor for the entries that are to be dismissed. The default value is *.displayed*.
- parameter completion: A completion handler that is to be called right after the entry is dismissed (After the animation is concluded).
*/
public func dismiss(_ descriptor: EntryDismissalDescriptor = .displayed, with completion: DismissCompletionHandler? = nil) {
DispatchQueue.main.async {
EKWindowProvider.shared.transform(to: view)
self.windowProvider.dismiss(descriptor, with: completion)
}
}

Expand All @@ -147,24 +250,32 @@ public final class SwiftEntryKit {
- parameter completion: A completion handler that is to be called right after the entry is dismissed (After the animation is concluded).
*/
public class func dismiss(_ descriptor: EntryDismissalDescriptor = .displayed, with completion: DismissCompletionHandler? = nil) {
DispatchQueue.main.async {
EKWindowProvider.shared.dismiss(descriptor, with: completion)
}
shared.dismiss(descriptor, with: completion)
}

/**
Layout the view hierarchy that is rooted in the window.
- In case you use complex animations, you can call it to refresh the AutoLayout mechanism on the entire view hierarchy.
- A thread-safe method - Can be invoked from any thread.
- A class method - Should be called on the class.
*/
public class func layoutIfNeeded() {
public func layoutIfNeeded() {
if Thread.isMainThread {
EKWindowProvider.shared.layoutIfNeeded()
windowProvider.layoutIfNeeded()
} else {
DispatchQueue.main.async {
EKWindowProvider.shared.layoutIfNeeded()
self.windowProvider.layoutIfNeeded()
}
}
}

/**
Layout the view hierarchy that is rooted in the window.
- In case you use complex animations, you can call it to refresh the AutoLayout mechanism on the entire view hierarchy.
- A thread-safe method - Can be invoked from any thread.
- A class method - Should be called on the class.
*/
public class func layoutIfNeeded() {
shared.layoutIfNeeded()
}
}