// // EntryViewController.swift // SwiftEntryKit // // Created by Daniel Huri on 4/19/18. // Copyright (c) 2018 huri000@gmail.com. All rights reserved. // import UIKit protocol EntryPresenterDelegate: AnyObject { var isResponsiveToTouches: Bool { set get } func displayPendingEntryOrRollbackWindow(dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?) } class EKRootViewController: UIViewController { // MARK: - Props private unowned let delegate: EntryPresenterDelegate private var lastAttributes: EKAttributes! private let backgroundView = EKBackgroundView() private lazy var wrapperView: EKWrapperView = { return EKWrapperView() }() /* Count the total amount of currently displaying entries, meaning, total subviews less one - the backgorund of the entry */ fileprivate var displayingEntryCount: Int { return view.subviews.count - 1 } fileprivate var isDisplaying: Bool { return lastEntry != nil } private var lastEntry: EKContentView? { return view.subviews.last as? EKContentView } private var isResponsive = false { didSet { wrapperView.isAbleToReceiveTouches = isResponsive delegate.isResponsiveToTouches = isResponsive } } override var shouldAutorotate: Bool { if lastAttributes == nil { return true } return lastAttributes.positionConstraints.rotation.isEnabled } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { guard let lastAttributes = lastAttributes else { return super.supportedInterfaceOrientations } switch lastAttributes.positionConstraints.rotation.supportedInterfaceOrientations { case .standard: return super.supportedInterfaceOrientations case .all: return .all } } // Previous status bar style private let previousStatusBar: EKAttributes.StatusBar private var statusBar: EKAttributes.StatusBar? = nil { didSet { if let statusBar = statusBar, ![statusBar, oldValue].contains(.ignored) { UIApplication.shared.set(statusBarStyle: statusBar) } } } override var preferredStatusBarStyle: UIStatusBarStyle { if [previousStatusBar, statusBar].contains(.ignored) { return super.preferredStatusBarStyle } return statusBar?.appearance.style ?? previousStatusBar.appearance.style } override var prefersStatusBarHidden: Bool { if [previousStatusBar, statusBar].contains(.ignored) { return super.prefersStatusBarHidden } return !(statusBar?.appearance.visible ?? previousStatusBar.appearance.visible) } // MARK: - Lifecycle required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } public init(with delegate: EntryPresenterDelegate) { self.delegate = delegate previousStatusBar = .currentStatusBar super.init(nibName: nil, bundle: nil) } override public func loadView() { view = wrapperView view.insertSubview(backgroundView, at: 0) backgroundView.isUserInteractionEnabled = false backgroundView.fillSuperview() } override public func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) statusBar = previousStatusBar } // Set status bar func setStatusBarStyle(for attributes: EKAttributes) { statusBar = attributes.statusBar } // MARK: - Setup func configure(entryView: EKEntryView) { // In case the entry is a view controller, add the entry as child of root if let viewController = entryView.content.viewController { addChild(viewController) } // Extract the attributes struct let attributes = entryView.attributes // Assign attributes let previousAttributes = lastAttributes // Remove the last entry removeLastEntry(lastAttributes: previousAttributes, keepWindow: true) lastAttributes = attributes let entryContentView = EKContentView(withEntryDelegate: self) view.addSubview(entryContentView) entryContentView.setup(with: entryView) switch attributes.screenInteraction.defaultAction { case .forward: isResponsive = false default: isResponsive = true } if previousAttributes?.statusBar != attributes.statusBar { setNeedsStatusBarAppearanceUpdate() } if shouldAutorotate { UIViewController.attemptRotationToDeviceOrientation() } } // Check priority precedence for a given entry func canDisplay(attributes: EKAttributes) -> Bool { guard let lastAttributes = lastAttributes else { return true } return attributes.precedence.priority >= lastAttributes.precedence.priority } // Removes last entry - can keep the window 'ON' if necessary private func removeLastEntry(lastAttributes: EKAttributes?, keepWindow: Bool) { guard let attributes = lastAttributes else { return } if attributes.popBehavior.isOverriden { lastEntry?.removePromptly() } else { popLastEntry() } } // Make last entry exit using exitAnimation - animatedly func animateOutLastEntry(completionHandler: SwiftEntryKit.DismissCompletionHandler? = nil) { lastEntry?.dismissHandler = completionHandler lastEntry?.animateOut(pushOut: false) } // Pops last entry (using pop animation) - animatedly func popLastEntry() { lastEntry?.animateOut(pushOut: true) } } // MARK: - UIResponder extension EKRootViewController { override func touchesEnded(_ touches: Set, with event: UIEvent?) { switch lastAttributes.screenInteraction.defaultAction { case .dismissEntry: lastEntry?.animateOut(pushOut: false) fallthrough default: lastAttributes.screenInteraction.customTapActions.forEach { $0() } } } } // MARK: - EntryScrollViewDelegate extension EKRootViewController: EntryContentViewDelegate { func didFinishDisplaying(entry: EKEntryView, keepWindowActive: Bool, dismissCompletionHandler: SwiftEntryKit.DismissCompletionHandler?) { guard !isDisplaying else { return } guard !keepWindowActive else { return } delegate.displayPendingEntryOrRollbackWindow(dismissCompletionHandler: dismissCompletionHandler) } func changeToInactive(withAttributes attributes: EKAttributes, pushOut: Bool) { guard displayingEntryCount <= 1 else { return } let clear = { let style = EKBackgroundView.Style(background: .clear, displayMode: attributes.displayMode) self.changeBackground(to: style, duration: attributes.exitAnimation.totalDuration) } guard pushOut else { clear() return } guard let lastBackroundStyle = lastAttributes?.screenBackground else { clear() return } if lastBackroundStyle != attributes.screenBackground { clear() } } func changeToActive(withAttributes attributes: EKAttributes) { let style = EKBackgroundView.Style(background: attributes.screenBackground, displayMode: attributes.displayMode) changeBackground(to: style, duration: attributes.entranceAnimation.totalDuration) } private func changeBackground(to style: EKBackgroundView.Style, duration: TimeInterval) { DispatchQueue.main.async { UIView.animate(withDuration: duration, delay: 0, options: [], animations: { self.backgroundView.style = style }, completion: nil) } } }