Skip to content
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()
}
}