diff --git a/AppRouter.podspec b/AppRouter.podspec
index 8f6a712..c2d8b3b 100755
--- a/AppRouter.podspec
+++ b/AppRouter.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "AppRouter"
- s.version = "3.0.3"
+ s.version = "4.0.0"
s.summary = "UIViewController creation, navigation, utility methods for easy routing"
s.homepage = "https://github.com/MLSDev/AppRouter"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1913257..5e84c11 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@
All notable changes to this project will be documented in this file.
+## [4.0.0](https://github.com/MLSDev/AppRouter/releases/tag/3.0.2)
+
+AppRouter and Presenter was refactored to provide additional flexibility
+
+
## [3.0.3](https://github.com/MLSDev/AppRouter/releases/tag/3.0.2)
Fixed bug with UIViewController instantiation inside UINavigationController
diff --git a/Plists/AppRouter.plist b/Plists/AppRouter.plist
index 49ca846..1bcfa6d 100755
--- a/Plists/AppRouter.plist
+++ b/Plists/AppRouter.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 3.0.2
+ 4.0.0
CFBundleSignature
????
CFBundleVersion
diff --git a/Sources/Core/AppRouter+accessors.swift b/Sources/Core/AppRouter+accessors.swift
index 64b25d4..f3e1ad7 100644
--- a/Sources/Core/AppRouter+accessors.swift
+++ b/Sources/Core/AppRouter+accessors.swift
@@ -3,23 +3,43 @@ import UIKit
extension AppRouter {
/// Current keyWindow rootViewController
- public class var rootViewController : UIViewController? {
- get { return AppRouter.window.rootViewController }
- set { AppRouter.window.rootViewController = newValue }
+ open var rootViewController : UIViewController? {
+ get { return window.rootViewController }
+ set { window.rootViewController = newValue }
}
/// Current topmost controller
- public class var topViewController : UIViewController? {
+ open var topViewController : UIViewController? {
return topViewController()
}
/// recursively tries to detect topmost controller
/// - parameter startingFrom: Specify controller which will be used as start point for searching
/// - returns: returns top-most controller if exists
- public class func topViewController(startingFrom base: UIViewController? = AppRouter.rootViewController) -> UIViewController? {
+ open func topViewController(startingFrom base: UIViewController? = rootViewController) -> UIViewController? {
if let topper = base?.toppestControllerFromCurrent() { return topViewController(startingFrom: topper) ?? base }
return base
}
+
+ // Backwards compatibility part
+
+ /// Current keyWindow rootViewController
+ public class var rootViewController : UIViewController? {
+ get { return AppRouter.shared.rootViewController }
+ set { AppRouter.shared.rootViewController = newValue }
+ }
+
+ /// Current topmost controller
+ public class var topViewController : UIViewController? {
+ return topViewController()
+ }
+
+ /// recursively tries to detect topmost controller
+ /// - parameter startingFrom: Specify controller which will be used as start point for searching
+ /// - returns: returns top-most controller if exists
+ public class func topViewController(startingFrom base: UIViewController? = rootViewController) -> UIViewController? {
+ return AppRouter.shared.topViewController(startingFrom: base)
+ }
}
public protocol ARToppestControllerProvider {
diff --git a/Sources/Core/AppRouter+animations.swift b/Sources/Core/AppRouter+animations.swift
index c326466..941f6b8 100644
--- a/Sources/Core/AppRouter+animations.swift
+++ b/Sources/Core/AppRouter+animations.swift
@@ -1,75 +1,104 @@
import Foundation
import UIKit
+extension AppRouterType {
+ var animator: AppRouter.Animators.RootAnimator { return .init(router: self) }
+}
+
extension AppRouter {
/// 🚲 Just few example animation methods
- public enum animations {
- /// Uses UIView.transitionFromView to animate AppRouter.rootViewController change.
- ///
- /// - parameter controller: controller becoming rootViewController.
- /// - parameter options: animation options used to animate transition.
- /// - parameter duration: animation duration
- /// - parameter callback: called after controller becomes rootViewController
- public static func setRootWithViewAnimation(_ controller: UIViewController, options: UIViewAnimationOptions = .transitionFlipFromLeft, duration: TimeInterval = 0.3, callback: Func? = nil) {
- if let rootController = AppRouter.rootViewController {
- let oldState = UIView.areAnimationsEnabled
- UIView.setAnimationsEnabled(false)
- UIView.transition(from: rootController.view, to: controller.view, duration: duration, options: options, completion: { state in
- AppRouter.rootViewController = controller
- UIView.setAnimationsEnabled(oldState)
- callback?(state)
- })
- } else {
- AppRouter.rootViewController = controller
- callback?(true)
+ public enum Animators {
+ public struct RootAnimator {
+ let router: AppRouterType
+
+ public func setRoot(controller: UIViewController, animation: AnimationType, callback: Func? = nil) {
+ switch animation {
+ case .none:
+ router.rootViewController = controller
+ callback?(true)
+ case .snapshotUpscale(let scaleTo, let opacityTo, let duration):
+ setRootWithSnapshotAnimation(controller, upscaleTo: scaleTo, opacityTo: opacityTo, duration: duration, callback: callback)
+ case .view(let options, let duration):
+ setRootWithViewAnimation(controller, options: options, duration: duration, callback: callback)
+ case .window(let options, let duration):
+ setRootWithWindowAnimation(controller, options: options, duration: duration, callback: callback)
+ }
}
- }
-
- /// Uses UIView.transitionWithView to animate AppRouter.rootViewController change.
- ///
- /// - parameter controller: controller becoming rootViewController
- /// - parameter options: animation options used to animate transition.
- /// - parameter duration: animation duration
- /// - parameter callback: called after controller becomes rootViewController
- public static func setRootWithWindowAnimation(_ controller: UIViewController, options: UIViewAnimationOptions = .transitionFlipFromLeft, duration: TimeInterval = 0.3, callback: Func? = nil) {
- if let _ = AppRouter.rootViewController {
- let oldState = UIView.areAnimationsEnabled
- UIView.setAnimationsEnabled(false)
- UIView.transition(with: AppRouter.window, duration: duration, options: options, animations: {
- AppRouter.rootViewController = controller
- }, completion: { state in
- UIView.setAnimationsEnabled(oldState)
- callback?(state)
- })
- } else {
- AppRouter.rootViewController = controller
- callback?(true)
+
+
+ /// Uses UIView.transitionFromView to animate router.rootViewController change.
+ ///
+ /// - parameter controller: controller becoming rootViewController.
+ /// - parameter options: animation options used to animate transition.
+ /// - parameter duration: animation duration
+ /// - parameter callback: called after controller becomes rootViewController
+ public func setRootWithViewAnimation(_ controller: UIViewController, options: UIViewAnimationOptions = .transitionFlipFromLeft, duration: TimeInterval = 0.3, callback: Func? = nil) {
+ if let rootController = router.rootViewController {
+ let oldState = UIView.areAnimationsEnabled
+ UIView.setAnimationsEnabled(false)
+ UIView.transition(from: rootController.view, to: controller.view, duration: duration, options: options, completion: { state in
+ self.router.rootViewController = controller
+ UIView.setAnimationsEnabled(oldState)
+ callback?(state)
+ })
+ } else {
+ router.rootViewController = controller
+ callback?(true)
+ }
+ }
+
+ /// Uses UIView.transitionWithView to animate router.rootViewController change.
+ ///
+ /// - parameter controller: controller becoming rootViewController
+ /// - parameter options: animation options used to animate transition.
+ /// - parameter duration: animation duration
+ /// - parameter callback: called after controller becomes rootViewController
+ public func setRootWithWindowAnimation(_ controller: UIViewController, options: UIViewAnimationOptions = .transitionFlipFromLeft, duration: TimeInterval = 0.3, callback: Func? = nil) {
+ if let _ = router.rootViewController {
+ let oldState = UIView.areAnimationsEnabled
+ UIView.setAnimationsEnabled(false)
+ UIView.transition(with: router.window, duration: duration, options: options, animations: {
+ self.router.rootViewController = controller
+ }, completion: { state in
+ UIView.setAnimationsEnabled(oldState)
+ callback?(state)
+ })
+ } else {
+ router.rootViewController = controller
+ callback?(true)
+ }
+ }
+
+ /// Uses UIView.animateWithDuration to animate router.rootViewController change.
+ ///
+ /// - parameter controller: controller becoming rootViewController
+ /// - parameter upscaleTo: final snapshot scale
+ /// - parameter opacityTo: final snapshot opacity
+ /// - parameter duration: animation duration
+ /// - parameter callback: called after controller becomes rootViewController
+ public func setRootWithSnapshotAnimation(_ controller: UIViewController, upscaleTo: CGFloat = 1.2, opacityTo: Float = 0, duration: TimeInterval = 0.3, callback: Func? = nil) {
+ if let _ = router.rootViewController, let snapshot:UIView = router.window.snapshotView(afterScreenUpdates: true) {
+ controller.view.addSubview(snapshot)
+ router.rootViewController = controller
+ UIView.animate(withDuration: duration, animations: {
+ snapshot.layer.opacity = opacityTo
+ snapshot.layer.transform = CATransform3DMakeScale(upscaleTo, upscaleTo, upscaleTo);
+ }, completion: { state in
+ snapshot.removeFromSuperview()
+ callback?(state)
+ })
+ } else {
+ router.rootViewController = controller
+ callback?(true)
+ }
}
}
- /// Uses UIView.animateWithDuration to animate AppRouter.rootViewController change.
- ///
- /// - parameter controller: controller becoming rootViewController
- /// - parameter upscaleTo: final snapshot scale
- /// - parameter opacityTo: final snapshot opacity
- /// - parameter duration: animation duration
- /// - parameter callback: called after controller becomes rootViewController
- public static func setRootWithSnapshotAnimation(_ controller: UIViewController, upscaleTo: CGFloat = 1.2, opacityTo: Float = 0, duration: TimeInterval = 0.3, callback: Func? = nil) {
- if let _ = AppRouter.rootViewController, let snapshot:UIView = AppRouter.window.snapshotView(afterScreenUpdates: true) {
- controller.view.addSubview(snapshot)
- AppRouter.rootViewController = controller
- UIView.animate(withDuration: duration, animations: {
- snapshot.layer.opacity = opacityTo
- snapshot.layer.transform = CATransform3DMakeScale(upscaleTo, upscaleTo, upscaleTo);
- }, completion: { state in
- snapshot.removeFromSuperview()
- callback?(state)
- })
- } else {
- AppRouter.rootViewController = controller
- callback?(true)
- }
+ public enum AnimationType {
+ case none
+ case view(options: UIViewAnimationOptions, duration: TimeInterval)
+ case window(options: UIViewAnimationOptions, duration: TimeInterval)
+ case snapshotUpscale(scaleTo: CGFloat, opacityTo: Float, duration: TimeInterval)
}
}
}
-
diff --git a/Sources/Core/AppRouter+instantiations.swift b/Sources/Core/AppRouter+instantiations.swift
index e232366..f7329fe 100644
--- a/Sources/Core/AppRouter+instantiations.swift
+++ b/Sources/Core/AppRouter+instantiations.swift
@@ -25,7 +25,7 @@ extension UIViewController {
return _instantiateFromXib(self, xibName: xibName ?? String(describing: self))
}
- fileprivate class func _instantiateFromXib(_ : T.Type, xibName : String) ->T {
+ fileprivate class func _instantiateFromXib(_ : T.Type, xibName : String) -> T {
return T(nibName: xibName, bundle: Bundle(for: self))
}
}
diff --git a/Sources/Core/AppRouter+navigations.swift b/Sources/Core/AppRouter+navigations.swift
index 2cbf00c..927e095 100644
--- a/Sources/Core/AppRouter+navigations.swift
+++ b/Sources/Core/AppRouter+navigations.swift
@@ -38,6 +38,24 @@ extension UIViewController {
}
return nil
}
+
+ /// Pop to previous controller in navigation stack. Do nothing if current is first
+ ///
+ /// - parameter animated: Set this value to true to animate the transition
+ /// - parameter completion: Called after transition ends successfully.
+ /// - returns: [UIViewCotnroller]? - returns the popped controllers
+ @discardableResult
+ public func pop(completion: Func?) -> [UIViewController]? {
+ return pop(animated: true, completion: completion)
+ }
+
+ /// Pop to previous controller in navigation stack. Do nothing if current is first
+ ///
+ /// - returns: [UIViewCotnroller]? - returns the popped controllers
+ @discardableResult
+ public func pop() -> [UIViewController]? {
+ return pop(animated: true)
+ }
/// Tries to close viewController by popping to previous in navigation stack or by dismissing if presented
///
@@ -45,7 +63,7 @@ extension UIViewController {
/// - parameter completion: Called after transition ends successfully
/// - returns: returns true if able to close
@discardableResult
- public func close(animated: Bool = true, completion: Func? = nil) -> Bool {
+ public func close(animated: Bool, completion: Func? = nil) -> Bool {
if canPop() {
_ = pop(animated: animated, completion: completion)
} else if isModal {
@@ -57,6 +75,23 @@ extension UIViewController {
return true
}
+ /// Tries to close viewController by popping to previous in navigation stack or by dismissing if presented
+ ///
+ /// - parameter completion: Called after transition ends successfully
+ /// - returns: returns true if able to close
+ @discardableResult
+ public func close(completion: Func?) -> Bool {
+ return close(animated: true, completion: completion)
+ }
+
+ /// Tries to close viewController by popping to previous in navigation stack or by dismissing if presented
+ ///
+ /// - returns: returns true if able to close
+ @discardableResult
+ public func close() -> Bool {
+ return close(animated: true, completion: nil)
+ }
+
fileprivate func canPop() -> Bool {
guard let stack = navigationController?.viewControllers , stack.count > 1 else { return false }
guard let first = stack.first , first != self else { return false }
diff --git a/Sources/Core/AppRouter+presenter.swift b/Sources/Core/AppRouter+presenter.swift
index bd79eb3..412fa25 100644
--- a/Sources/Core/AppRouter+presenter.swift
+++ b/Sources/Core/AppRouter+presenter.swift
@@ -1,207 +1,356 @@
import Foundation
import UIKit
-/// Presenter aggregator class
-open class ViewControllerPresentConfiguration {
- /// Provides target on which presentation will be applied
- open var target : ARControllerProvider = ARPresentationTarget.top
-
- /// Provides controller which will be configured, embedded and presented
- open var source : ARControllerProvider = ARPresentationSource.storyboard(initial: true)
-
- /// Embeds source inside container (UINavigationController, UITabBarController, etc) which will be used for presentation
- open var embedder : (T) -> UIViewController? = { $0 }
-
- /// Configure source controller before presentation
- open var configurator: (T) -> () = { _ in }
-
- /// Declare AppRouter.topViewController to be a **target** provider
- open func onTop() -> ViewControllerPresentConfiguration {
- target = ARPresentationTarget.top
- return self
- }
-
- /// Declare AppRouter.rootViewController to be a **target** provider
- open func onRoot() -> ViewControllerPresentConfiguration {
- target = ARPresentationTarget.root
- return self
- }
-
- /// Declare custom **target** provider
- ///
- /// - parameter targetBlock: block should return target viewController which will be used for presentation
- open func onCustom(_ targetBlock : @escaping () -> UIViewController?) -> ViewControllerPresentConfiguration {
- target = ARPresentationTarget.anonymous(targetBlock)
- return self
- }
-
- /// Declare **source** provider to take controller from storyboard
- ///
- /// - parameter name: Storyboard name. Default value: controller type
- /// - parameter initial: Set this value if controller is initial in storyboard or it's rootController on initial UINavigationController
- open func fromStoryboard(_ name: String? = nil, initial : Bool = true) -> ViewControllerPresentConfiguration {
- if let name = name { source = ARPresentationSource.customStoryboard(name: name, inital: initial) }
- else { source = ARPresentationSource.storyboard(initial: initial) }
- return self
- }
-
- /// Declare **source** provider to take controller from xib
- ///
- /// - parameter name: Xib name. Default value: contollers type
- open func fromXib(_ name: String? = nil) -> ViewControllerPresentConfiguration {
- if let name = name { source = ARPresentationSource.customXib(name) }
- else { source = ARPresentationSource.xib }
- return self
- }
-
- /// Declare **configuration** block which used to configure controller before presentation
- ///
- /// - parameter configuration: block allows to apply additional configuration before presenting
- open func configure(_ configurator: @escaping (T) -> ()) -> ViewControllerPresentConfiguration {
- self.configurator = configurator
- return self
- }
-
- /// Declare **embedder** provider to embed controller in simple UINavigationController before presentation
- ///
- /// - parameter navigationController: set custom UINavigationController to be used
- open func embedInNavigation(_ navigationController: UINavigationController = UINavigationController()) -> ViewControllerPresentConfiguration {
- embedder = { source in
- navigationController.viewControllers.append(source)
- return navigationController
- }
- return self
+extension AppRouter {
+ public enum Presenter {
+ /// Factory for Configurations construction. Can be replaced with your own.
+ public static var configurationFactory: ARPresentConfigurationFactory = AppRouter.Presenter.DefaultBuilder()
}
-
- /// Declare **embedder** provider to embed controller in UITabBarController before presentation
- ///
- /// - parameter tabBarController: UITabBarController - used as container of source controller
- open func embedInTabBar(_ tabBarController: UITabBarController) -> ViewControllerPresentConfiguration {
- embedder = { source in
- var originalCollection = tabBarController.viewControllers ?? []
- originalCollection.append(source)
- tabBarController.viewControllers = originalCollection
- return tabBarController
- }
- return self
- }
-
- /// Custom anonymous **embedder** provider
- ///
- /// - parameter embederBlock: block should return UIViewController which will be used as presentation target
- open func embedIn(_ embederBlock: @escaping (T) -> UIViewController?) -> ViewControllerPresentConfiguration {
- embedder = embederBlock
- return self
- }
-
- /// Push current configuration
- ///
- /// - parameter animated: Set this value to true to animate the transition.
- /// - parameter completion: The block to execute after the view controller is pushed.
- /// - returns: returns instance provided by `source` provider
- @discardableResult
- open func push(animated: Bool = true, completion: Func? = nil) -> T? {
- guard let sourceController = source.provideController(T.self), let parent = provideEmbeddedController(sourceController) else { debug("error constructing source controller"); return nil }
- configurator(sourceController)
- guard let targetController = target.provideController(UIViewController.self) else { debug("error fetching target controller"); return nil }
- guard let targetNavigation = (targetController as? UINavigationController) ?? targetController.navigationController else { debug("error fetching navigation controller"); return nil }
- targetNavigation.pushViewController(parent, animated: animated, completion: completion)
- return sourceController
- }
-
- /// Present current configuration
- ///
- /// - parameter animated: Set this value to true to animate the transition.
- /// - parameter completion: The block to execute after the view controller is presented.
- /// - returns: returns instance provided by `source` provider
- @discardableResult
- open func present(animated: Bool = true, completion: Func? = nil) -> T? {
- guard let sourceController = source.provideController(T.self), let parent = provideEmbeddedController(sourceController) else { debug("error constructing source controller"); return nil }
- configurator(sourceController)
- guard let targetController = target.provideController(UIViewController.self) else { debug("error fetching target controller"); return nil }
- targetController.present(parent, animated: animated, completion: completion)
- return sourceController
+}
+
+/// Used for PresentConfiguration construction
+public protocol ARPresentConfigurationFactory {
+ func buildPresenter() -> AppRouter.Presenter.Configuration
+}
+
+public protocol AppRouterType: class {
+ var window: UIWindow { get }
+ var topViewController: UIViewController? { get }
+ var rootViewController: UIViewController? { get set }
+}
+
+extension AppRouter: AppRouterType {}
+
+extension AppRouter.Presenter {
+ /// Presenter aggregator class
+ open class Configuration {
+ /// Base router to work with
+ var router: AppRouterType
+
+ /// Provides target on which presentation will be applied
+ var targetProvider : () throws -> UIViewController
+
+ /// Provides controller which will be configured, embedded and presented
+ var sourceProvider : () throws -> T = PresentationSource.storyboard(initial: true).provideController
+
+ /// Embeds source inside container (UINavigationController, UITabBarController, etc) which will be used for presentation
+ var embedder : (T) throws -> UIViewController = { $0 }
+
+ /// Configure source controller before presentation
+ var configurator: (T) throws -> () = { _ in }
+
+ /// Declare router.topViewController to be a **target** provider
+ open func onTop() -> Self {
+ targetProvider = PresentationTarget.top(router).provideController
+ return self
+ }
+
+ /// Declare router.rootViewController to be a **target** provider
+ open func onRoot() -> Self {
+ targetProvider = PresentationTarget.root(router).provideController
+ return self
+ }
+
+ /// Declare custom **target** provider
+ ///
+ /// - parameter provider: block should return target viewController which will be used for presentation
+ open func on(_ provider: @escaping () throws -> UIViewController) -> Self {
+ targetProvider = provider
+ return self
+ }
+
+ /// Declare **source** provider to take controller from storyboard
+ ///
+ /// - parameter name: Storyboard name. Default value: controller type
+ /// - parameter initial: Set this value if controller is initial in storyboard or it's rootController on initial UINavigationController
+ open func fromStoryboard(_ name: String? = nil, initial : Bool = true) -> Self {
+ if let name = name { sourceProvider = PresentationSource.customStoryboard(name: name, inital: initial).provideController }
+ else { sourceProvider = PresentationSource.storyboard(initial: initial).provideController }
+ return self
+ }
+
+ /// Declare **source** provider to take controller from xib
+ ///
+ /// - parameter name: Xib name. Default value: contollers type
+ open func fromXib(_ name: String? = nil) -> Self {
+ if let name = name { sourceProvider = PresentationSource.customXib(name).provideController }
+ else { sourceProvider = PresentationSource.xib.provideController }
+ return self
+ }
+
+ /// Declare **source** factory to take controller from
+ ///
+ /// - parameter provider: closure that providers source controller
+ open func from(provider: @escaping () throws -> T) -> Self {
+ sourceProvider = provider
+ return self
+ }
+
+ /// Declare **configuration** block which used to configure controller before presentation
+ ///
+ /// - parameter configuration: block allows to apply additional configuration before presenting
+ open func configure(_ configuration: @escaping (T) throws -> ()) -> Self {
+ self.configurator = configuration
+ return self
+ }
+
+ /// Declare **embedder** provider to embed controller in simple UINavigationController before presentation
+ ///
+ /// - parameter navigationController: set custom UINavigationController to be used
+ open func embedInNavigation(_ navigationController: UINavigationController = UINavigationController()) -> Self {
+ embedder = { source in
+ navigationController.viewControllers.append(source)
+ return navigationController
+ }
+ return self
+ }
+
+ /// Declare **embedder** provider to embed controller in UITabBarController before presentation
+ ///
+ /// - parameter tabBarController: UITabBarController - used as container of source controller
+ open func embedInTabBar(_ tabBarController: UITabBarController) -> Self {
+ embedder = { source in
+ tabBarController.viewControllers = tabBarController.viewControllers ?? [] + [source]
+ return tabBarController
+ }
+ return self
+ }
+
+ /// Custom anonymous **embedder** provider
+ ///
+ /// - parameter embederBlock: block should return UIViewController which will be used as presentation target
+ open func embedIn(_ embederBlock: @escaping (T) throws -> UIViewController) -> Self {
+ embedder = embederBlock
+ return self
+ }
+
+ /// Push current configuration
+ ///
+ /// - parameter animated: Set this value to true to animate the transition.
+ /// - parameter completion: The block to execute after the view controller is pushed.
+ /// - returns: returns instance provided by `source` provider
+ @discardableResult
+ open func push(animated: Bool, completion: Func? = nil) -> T? {
+ do {
+ let embedded = try provideEmbeddedSourceController()
+ guard !(embedded.parent is UINavigationController) else { throw Errors.tryingToPushNavigationController }
+ let targetController = try performTargetConstruction() as UIViewController
+ let targetNavigation = try targetController as? UINavigationController ??
+ targetController.navigationController ??
+ Errors.failedToFindNavigationControllerToPushOn.rethrow()
+ targetNavigation.pushViewController(embedded.parent, animated: animated, completion: completion)
+ return embedded.child
+ } catch {
+ AppRouter.print(error.localizedDescription)
+ return nil
+ }
+ }
+
+ /// Push current configuration
+ ///
+ /// - returns: returns instance provided by `source` provider
+ @discardableResult
+ open func push() -> T? {
+ return push(animated: true)
+ }
+
+ /// Present current configuration
+ ///
+ /// - parameter animated: Set this value to true to animate the transition.
+ /// - parameter completion: The block to execute after the view controller is presented.
+ /// - returns: returns instance provided by `source` provider
+ @discardableResult
+ open func present(animated: Bool, completion: Func? = nil) -> T? {
+ do {
+ let embedded = try provideEmbeddedSourceController()
+ let targetController = try performTargetConstruction() as UIViewController
+ targetController.present(embedded.parent, animated: animated, completion: completion)
+ return embedded.child
+ } catch {
+ AppRouter.print(error.localizedDescription)
+ return nil
+ }
+ }
+
+ /// Present current configuration
+ ///
+ /// - returns: returns instance provided by `source` provider
+ @discardableResult
+ open func present() -> T? {
+ return present(animated: true)
+ }
+
+ /// Set embedded controller as rootViewController
+ ///
+ /// - parameter animation: Animation configuration
+ /// - parameter completion: The block to execute after the view controller is setted.
+ /// - returns: returns instance provided by `source` provider
+ @discardableResult
+ open func setAsRoot(animation: AppRouter.Animators.AnimationType, completion: Func? = nil) -> T? {
+ do {
+ let embedded = try provideEmbeddedSourceController()
+ router.animator.setRoot(controller: embedded.parent, animation: animation, callback: completion)
+ return embedded.child
+ } catch {
+ AppRouter.print(error.localizedDescription)
+ return nil
+ }
+ }
+
+ /// Set embedded controller as rootViewController with window crossDissolve animation
+ ///
+ /// - returns: returns instance provided by `source` provider
+ @discardableResult
+ open func setAsRoot() -> T? {
+ return setAsRoot(animation: .window(options: .transitionCrossDissolve, duration: 0.3))
+ }
+
+ /// Provides source controller already configured for use.
+ ///
+ /// - returns: controller created from source.
+ open func provideSourceController() throws -> T {
+ let sourceController = try performSourceConstruction()
+ try performConfiguration(for: sourceController)
+ return sourceController
+ }
+
+ /// Provides source controller embedded in `embedder` controller and configured for use.
+ ///
+ /// - returns: embedded controller.
+ open func provideEmbeddedSourceController() throws -> (child: T, parent: UIViewController) {
+ let sourceController = try performSourceConstruction()
+ let embedded = try performEmbed(for: sourceController)
+ try performConfiguration(for: sourceController)
+ return (child: sourceController, parent: embedded)
+ }
+
+ /// Override point to perform additional logic while constructing source controller
+ ///
+ /// - returns: source controller.
+ open func performSourceConstruction() throws -> T {
+ return try sourceProvider()
+ }
+
+ /// Override point to perform additional logic while constructing target controller
+ ///
+ /// - returns: target controller.
+ open func performTargetConstruction() throws -> U {
+ return try targetProvider() as? U ?? Errors.failedToConstructTargetController.rethrow()
+ }
+
+ /// Override point to perform additional logic while embedding source controller
+ ///
+ /// - returns: parent controller
+ open func performEmbed(for source: T) throws -> UIViewController {
+ return try embedder(source)
+ }
+
+ /// Override point to perform additional logic while configuring source controller
+ ///
+ /// - parameter for: source controller to perform configuration on
+ open func performConfiguration(for source: T) throws -> Void {
+ if #available(iOS 9.0, *) {
+ source.loadViewIfNeeded()
+ } else {
+ _ = source.view
+ }
+ return try configurator(source)
+ }
+
+ public init(router: AppRouterType = AppRouter.shared) {
+ self.router = router
+ self.targetProvider = PresentationTarget.top(router).provideController
+ }
}
- /// Provides source controller already configured for use.
- ///
- /// - returns: controller created from source.
- open func provideSourceController() -> T? {
- guard let sourceController = source.provideController(T.self) else { debug("error constructing source controller"); return nil }
- configurator(sourceController)
- return sourceController
+ public enum Errors: LocalizedError {
+ case failedToConstructSourceController
+ case failedToConstructTargetController
+ case failedToEmbedSourceController
+ case failedToFindNavigationControllerToPushOn
+ case tryingToPushNavigationController
+
+ public var errorDescription: String? {
+ switch self {
+ case .failedToConstructSourceController:
+ return "[AppRouter][Presenter] failed to construct source controller."
+ case .failedToConstructTargetController:
+ return "[AppRouter][Presenter] failed to construct target controller."
+ case .failedToEmbedSourceController:
+ return "[AppRouter][Presenter] failed to embed source controller."
+ case .failedToFindNavigationControllerToPushOn:
+ return "[AppRouter][Presenter] failed to find navigation controller (using target provider) to push on."
+ case .tryingToPushNavigationController:
+ return "[AppRouter][Presenter] trying to push navigation controller (provided by source provider)."
+ }
+ }
}
- /// Provides source controller embedded in `embedder` controller and configured for use.
- ///
- /// - returns: embedded controller.
- open func provideEmbeddedSourceController() -> UIViewController? {
- guard let sourceController = source.provideController(T.self) else { debug("error constructing source controller"); return nil }
- guard let embedded = provideEmbeddedController(sourceController) else { return nil }
- configurator(sourceController)
- return embedded
+ public enum PresentationTarget {
+ case top(AppRouterType)
+ case root(AppRouterType)
+ case anonymous(() throws -> UIViewController)
+ public func provideController() throws -> T {
+ switch self {
+ case .top(let router):
+ return try router.topViewController as? T ?? Errors.failedToConstructTargetController.rethrow()
+ case .root(let router):
+ return try router.rootViewController as? T ?? Errors.failedToConstructTargetController.rethrow()
+ case .anonymous(let provider):
+ return try provider() as? T ?? Errors.failedToConstructTargetController.rethrow()
+ }
+ }
}
- fileprivate func provideEmbeddedController(_ sourceController: T) -> UIViewController? {
- guard let parent = embedder(sourceController) else { debug("error embedding controller"); return nil }
- return parent
+ public enum PresentationSource {
+ case storyboard(initial: Bool)
+ case xib
+ case customStoryboard(name: String, inital: Bool)
+ case customXib(String)
+ case preconstructed(UIViewController)
+ case anonymous(() throws -> UIViewController)
+ public func provideController() throws -> T where T : BundleForClassInstantiable {
+ switch self {
+ case .storyboard(let initial):
+ return try T.instantiate(initial: initial) ?? Errors.failedToConstructSourceController.rethrow()
+ case .customStoryboard(let name, let initial):
+ return try T.instantiate(storyboardName: name, initial: initial) ?? Errors.failedToConstructSourceController.rethrow()
+ case .xib:
+ return try T.instantiateFromXib() ?? Errors.failedToConstructSourceController.rethrow()
+ case .customXib(let name):
+ return try T.instantiateFromXib(name) ?? Errors.failedToConstructSourceController.rethrow()
+ case .preconstructed(let vc):
+ return try vc as? T ?? Errors.failedToConstructSourceController.rethrow()
+ case .anonymous(let provider):
+ return try provider() as? T ?? Errors.failedToConstructSourceController.rethrow()
+ }
+ }
}
- fileprivate func debug(_ str: String) {
- AppRouter.print("#[Presenter<\(T.self)>] " + str)
- }
-}
-
-enum ARPresentationTarget : ARControllerProvider{
- case top
- case root
- case anonymous(() -> UIViewController?)
- func provideController(_ type: T.Type) -> T? {
- switch self {
- case .top: return AppRouter.topViewController() as? T
- case .root: return AppRouter.rootViewController as? T
- case .anonymous(let provider): return provider() as? T
+ internal struct DefaultBuilder: ARPresentConfigurationFactory {
+ func buildPresenter() -> AppRouter.Presenter.Configuration where T : UIViewController {
+ return .init()
}
}
}
-enum ARPresentationSource : ARControllerProvider {
- case storyboard(initial: Bool)
- case xib
- case customStoryboard(name: String, inital: Bool)
- case customXib(String)
- case preconstructed(UIViewController)
- func provideController(_ type: T.Type) -> T? where T : BundleForClassInstantiable {
- switch self {
- case .storyboard(let initial): return T.instantiate(initial: initial)
- case .customStoryboard(let name, let initial): return T.instantiate(storyboardName: name, initial: initial)
- case .xib: return T.instantiateFromXib()
- case .customXib(let name): return T.instantiateFromXib(name)
- case .preconstructed(let vc): return vc as? T
- }
+extension Error {
+ internal func rethrow() throws -> T {
+ throw self
}
}
-/// Used for source and target controller providing
-public protocol ARControllerProvider {
- /// It should return controller instance of specified type
- func provideController(_ type: T.Type) -> T?
-}
-
-
/// Workaround to use Self as generic constraint in method
public protocol ARControllerConfigurableProtocol : class {}
extension UIViewController : ARControllerConfigurableProtocol {}
extension ARControllerConfigurableProtocol where Self: UIViewController {
/// Presentation configurator. Defaults: -onTop -fromStoryboard
- public static func presenter() -> ViewControllerPresentConfiguration {
- return ViewControllerPresentConfiguration()
+ public static func presenter() -> AppRouter.Presenter.Configuration {
+ return AppRouter.Presenter.configurationFactory.buildPresenter()
}
- /// Presentation configurator with current instance used as source. Default target - onTop
- public func presenter() -> ViewControllerPresentConfiguration {
- let configuration : ViewControllerPresentConfiguration = ViewControllerPresentConfiguration()
- configuration.source = ARPresentationSource.preconstructed(self)
- return configuration
+ /// Presentation configurator with current instance used as source. Default target - onTop. Warrning - current controller instance will be captured.
+ public func presenter() -> AppRouter.Presenter.Configuration {
+ return AppRouter.Presenter.configurationFactory.buildPresenter().from{ self }
}
}
diff --git a/Sources/Core/AppRouter.swift b/Sources/Core/AppRouter.swift
index c092052..ac5ecc6 100644
--- a/Sources/Core/AppRouter.swift
+++ b/Sources/Core/AppRouter.swift
@@ -5,35 +5,37 @@ public typealias Func = (T) -> U
/// Namespacing class
open class AppRouter {
+ /// Provides default AppRouter instance
+ public static var shared = AppRouter()
+
/// Provides application keyWindow. In normal cases returns UIApplication.sharedApplication().delegate?.window if available or creates new one if not.
/// If appDelegate does not implement UIApplicationDelegate.window property - returns UIApplication.sharedApplication().keyWindow
- open class var window: UIWindow {
- guard let delegate = UIApplication.shared.delegate else { fatalError("no appDelegate found") }
- if let windowProperty = delegate.window {
- if let window = windowProperty {
- return window
- } else {
- let newWindow = UIWindow(frame: UIScreen.main.bounds)
- delegate.perform(#selector(setter: UIApplicationDelegate.window), with: newWindow)
- newWindow.makeKeyAndVisible()
- return newWindow
- }
- } else {
- guard let window = UIApplication.shared.keyWindow else { fatalError("delegate doesn't implement window property and no UIApplication.sharedApplication().keyWindow available") }
- return window
- }
+ public static var window: UIWindow {
+ return shared.window
}
- public init() {}
+ /// Current window which Router work with
+ open var window: UIWindow {
+ return windowProvider()
+ }
+ open var windowProvider: () -> UIWindow
+
+ public convenience init() {
+ self.init(windowProvider: WindowProvider.dynamic.window)
+ }
+
+ public init(windowProvider: @escaping () -> UIWindow) {
+ self.windowProvider = windowProvider
+ }
/// Defines AppRouter output target
- open static var debugOutput: ARDebugOutputProtocol = DebugOutput.nsLog
+ open static var debugOutput: (String) -> () = DebugOutput.nsLog.debugOutput
internal static func print(_ str: String) {
- debugOutput.debugOutput(str)
+ debugOutput(str)
}
/// Few predefined debugOutput targets
- public enum DebugOutput : ARDebugOutputProtocol {
+ public enum DebugOutput {
/// hides output
case none
@@ -51,9 +53,31 @@ open class AppRouter {
}
}
}
-}
-
-/// AppRouter protocol used to specify proper debug outup mechanic
-public protocol ARDebugOutputProtocol {
- func debugOutput(_ str: String)
+
+ public enum WindowProvider {
+ case `static`(UIWindow)
+ case dynamic
+
+ func window() -> UIWindow {
+ switch self {
+ case .static(let window):
+ return window
+ case .dynamic:
+ guard let delegate = UIApplication.shared.delegate else { fatalError("no appDelegate found") }
+ if let windowProperty = delegate.window {
+ if let window = windowProperty {
+ return window
+ } else {
+ let newWindow = UIWindow(frame: UIScreen.main.bounds)
+ delegate.perform(#selector(setter: UIApplicationDelegate.window), with: newWindow)
+ newWindow.makeKeyAndVisible()
+ return newWindow
+ }
+ } else {
+ guard let window = UIApplication.shared.keyWindow else { fatalError("delegate doesn't implement window property and no UIApplication.sharedApplication().keyWindow available") }
+ return window
+ }
+ }
+ }
+ }
}
diff --git a/Tests/Accessors/AppRouterAccessorsTests.swift b/Tests/Accessors/AppRouterAccessorsTests.swift
index 690c53a..a0817db 100644
--- a/Tests/Accessors/AppRouterAccessorsTests.swift
+++ b/Tests/Accessors/AppRouterAccessorsTests.swift
@@ -11,7 +11,7 @@ import XCTest
class AppRouterAccessorsTests: XCTestCase {
- func testHierarchyAccessors() {
+ func testHierarchyAccessors() throws {
XCTAssertNil(AppRouter.rootViewController)
XCTAssertNil(AppRouter.topViewController)
diff --git a/Tests/Animations/AppRouterAnimationsTests.swift b/Tests/Animations/AppRouterAnimationsTests.swift
index 2d9b200..29423d2 100644
--- a/Tests/Animations/AppRouterAnimationsTests.swift
+++ b/Tests/Animations/AppRouterAnimationsTests.swift
@@ -12,18 +12,18 @@ import XCTest
class AppRouterAnimationsTests: XCTestCase {
override func setUp() {
- AppRouter.rootViewController = nil
+ AppRouter.shared.rootViewController = nil
}
func testViewAnimation() {
let first = AppRouterPresenterBaseController()
let second = AppRouterPresenterAdditionalController()
- AppRouter.animations.setRootWithViewAnimation(first, duration: 0)
+ AppRouter.shared.animator.setRootWithViewAnimation(first, duration: 0)
XCTAssertTrue(AppRouter.rootViewController == first)
let expectation = self.expectation(description: "")
- AppRouter.animations.setRootWithViewAnimation(second, duration: 0, callback: { _ in
+ AppRouter.shared.animator.setRootWithViewAnimation(second, duration: 0, callback: { _ in
XCTAssertTrue(AppRouter.rootViewController == second)
expectation.fulfill()
})
@@ -34,11 +34,11 @@ class AppRouterAnimationsTests: XCTestCase {
let first = AppRouterPresenterBaseController()
let second = AppRouterPresenterAdditionalController()
- AppRouter.animations.setRootWithWindowAnimation(first, duration: 0)
+ AppRouter.shared.animator.setRootWithWindowAnimation(first, duration: 0)
XCTAssertTrue(AppRouter.rootViewController == first)
let expectation = self.expectation(description: "")
- AppRouter.animations.setRootWithWindowAnimation(second, duration: 0, callback: { _ in
+ AppRouter.shared.animator.setRootWithWindowAnimation(second, duration: 0, callback: { _ in
XCTAssertTrue(AppRouter.rootViewController == second)
expectation.fulfill()
})
@@ -49,11 +49,11 @@ class AppRouterAnimationsTests: XCTestCase {
let first = AppRouterPresenterBaseController()
let second = AppRouterPresenterAdditionalController()
- AppRouter.animations.setRootWithSnapshotAnimation(first, duration: 0)
+ AppRouter.shared.animator.setRootWithSnapshotAnimation(first, duration: 0)
XCTAssertTrue(AppRouter.rootViewController == first)
let expectation = self.expectation(description: "")
- AppRouter.animations.setRootWithSnapshotAnimation(second, duration: 0, callback: { _ in
+ AppRouter.shared.animator.setRootWithSnapshotAnimation(second, duration: 0, callback: { _ in
XCTAssertTrue(AppRouter.rootViewController == second)
expectation.fulfill()
})
diff --git a/Tests/Presenter/AppRouterPresenterTests.swift b/Tests/Presenter/AppRouterPresenterTests.swift
index f72da00..2766e89 100644
--- a/Tests/Presenter/AppRouterPresenterTests.swift
+++ b/Tests/Presenter/AppRouterPresenterTests.swift
@@ -10,9 +10,9 @@ import XCTest
@testable import AppRouter
class AppRouterPresenterTests: XCTestCase {
- weak var tabBarController: AppRouterPresenterTabBarController?
- weak var navController: AppRouterPresenterNavigationController?
- weak var baseController: AppRouterPresenterBaseController?
+ weak var tabBarController: AppRouterPresenterTabBarController!
+ weak var navController: AppRouterPresenterNavigationController!
+ weak var baseController: AppRouterPresenterBaseController!
override func setUp() {
tabBarController = AppRouterPresenterTabBarController.instantiate(storyboardName: "AppRouterPresenterControllers", initial: true)
@@ -29,118 +29,121 @@ class AppRouterPresenterTests: XCTestCase {
}
func testPresenterUtilityTargetMethods() {
- let presenter = UIViewController.presenter()
- _ = presenter.onTop()
- XCTAssertTrue(presenter.target.provideController(UIViewController.self) == baseController)
- _ = presenter.onRoot()
- XCTAssertTrue(presenter.target.provideController(UIViewController.self) == tabBarController)
- _ = presenter.onCustom({ self.baseController })
- XCTAssertTrue(presenter.target.provideController(UIViewController.self) == baseController)
+ XCTAssertTrue(try UIViewController.presenter().onTop().performTargetConstruction() == baseController)
+ XCTAssertTrue(try UIViewController.presenter().onRoot().performTargetConstruction() == tabBarController)
+ XCTAssertTrue(try UIViewController.presenter().on{ self.baseController }.performTargetConstruction() == baseController)
}
func testPresenterUtilitySourceMethods() {
- let presenter = AppRouterPresenterAdditionalController.presenter()
- _ = presenter.fromXib()
- XCTAssertNotNil(presenter.source.provideController(AppRouterPresenterAdditionalController.self))
- _ = presenter.fromXib("AppRouterPresenterAdditionalController")
- XCTAssertNotNil(presenter.source.provideController(AppRouterPresenterAdditionalController.self))
- _ = presenter.fromStoryboard("AppRouterPresenterControllers", initial: false)
- XCTAssertNotNil(presenter.source.provideController(AppRouterPresenterAdditionalController.self))
-
- let initialPresenter = StoryboardWithInitialViewController.presenter()
- _ = initialPresenter.fromStoryboard()
- XCTAssertNotNil(initialPresenter.source.provideController(StoryboardWithInitialViewController.self))
+ XCTAssertNotNil(try AppRouterPresenterAdditionalController.presenter().fromXib().performSourceConstruction())
+ XCTAssertNotNil(try AppRouterPresenterAdditionalController.presenter().fromXib("AppRouterPresenterAdditionalController").performSourceConstruction())
+ XCTAssertNotNil(try AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).performSourceConstruction())
+ XCTAssertNotNil(try StoryboardWithInitialViewController.presenter().fromStoryboard().performSourceConstruction())
}
-
+
func testPresenterUtilityConfigurationMethods() {
- let presenter = AppRouterPresenterBaseController.presenter()
- guard let base = baseController else { return XCTFail() }
- presenter.configurator(base)
- XCTAssertFalse(base.initialized)
- _ = presenter.configure({ $0.initialized = true })
- presenter.configurator(base)
- XCTAssertTrue(base.initialized)
+ XCTAssertFalse(try baseController.presenter().provideSourceController().initialized)
+ XCTAssertTrue(try baseController.presenter().configure({ $0.initialized = true }).provideSourceController().initialized)
}
- func testPresenterProvideSourceController() {
- let presenter = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false)
- guard let base = baseController else { return XCTFail() }
- XCTAssertFalse(base.initialized)
- _ = presenter.configure({ $0.initialized = true })
- let source = presenter.provideSourceController()
- XCTAssertTrue(source?.initialized == true)
+ func testPresenterProvideSourceController() throws {
+ XCTAssertFalse(baseController.initialized)
+ let presenter = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).configure({ $0.initialized = true })
+ XCTAssertTrue(try presenter.provideSourceController().initialized == true)
}
- func testPresenterProvideEmbeddedSourceController() {
- let presenter = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false)
+ func testPresenterProvideEmbeddedSourceController() throws {
+ XCTAssertFalse(baseController.initialized)
let nav = UINavigationController()
- guard let base = baseController else { return XCTFail() }
- XCTAssertFalse(base.initialized)
- _ = presenter.configure({
- $0.initialized = true
- $0.navigationController?.title = "TestTitle"
- }).embedInNavigation(nav)
- let embedded = presenter.provideEmbeddedSourceController()
- XCTAssertTrue(embedded === nav)
- guard let embeddedNav = embedded as? UINavigationController else { return XCTFail() }
- guard let first = embeddedNav.visibleViewController as? AppRouterPresenterAdditionalController else { return XCTFail() }
- XCTAssertTrue(first.initialized == true)
+ let presenter = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false)
+ .configure({
+ $0.initialized = true
+ $0.navigationController?.title = "TestTitle"
+ }).embedInNavigation(nav)
+
+ let embedded = try presenter.provideEmbeddedSourceController()
+ XCTAssertTrue(embedded.parent === nav)
+ guard let embeddedNav = embedded.parent as? UINavigationController else { return XCTFail() }
+ guard let visible = embeddedNav.visibleViewController as? AppRouterPresenterAdditionalController else { return XCTFail() }
+ XCTAssertTrue(visible.initialized == true)
XCTAssertTrue(embeddedNav.title == "TestTitle")
+ XCTAssertTrue(embedded.child === visible)
}
- func testPresenterUtilityEmbeddingMethods() {
- let presenter = AppRouterPresenterAdditionalController.presenter()
+ func testPresenterUtilityEmbedInNavigation() throws {
let controller = AppRouterPresenterAdditionalController()
- _ = presenter.embedInNavigation()
- guard let navigation = presenter.embedder(controller) as? UINavigationController else { return XCTFail() }
+ let presenter = controller.presenter().embedInNavigation()
+ let embed = try presenter.provideEmbeddedSourceController()
+ guard let navigation = embed.parent as? UINavigationController else { return XCTFail() }
XCTAssertTrue(navigation.topViewController == controller)
-
- guard let customNavigation = navController else { return XCTFail() }
- _ = presenter.embedInNavigation(customNavigation)
- guard let embeddedCustom = presenter.embedder(controller) as? UINavigationController else { return XCTFail() }
- XCTAssertTrue(embeddedCustom is AppRouterPresenterNavigationController)
- XCTAssertTrue(embeddedCustom.topViewController == controller)
-
-
- guard let customTabBar = tabBarController else { return XCTFail() }
- _ = presenter.embedInTabBar(customTabBar)
- guard let embeddedCustomTabBar = presenter.embedder(controller) as? UITabBarController else { return XCTFail() }
- XCTAssertTrue(embeddedCustomTabBar is AppRouterPresenterTabBarController)
- XCTAssertTrue(embeddedCustomTabBar.viewControllers?.last == controller)
-
- _ = presenter.embedIn({ $0 })
- XCTAssertTrue(presenter.embedder(controller) == controller)
-
+ }
+
+ func testPresenterUtilityEmbedInCustomNavigation() throws {
+ let controller = AppRouterPresenterAdditionalController()
+ let navigation = UINavigationController()
+ let presenter = controller.presenter().embedInNavigation(navigation)
+ let embed = try presenter.provideEmbeddedSourceController()
+ guard let nav = embed.parent as? UINavigationController else { return XCTFail() }
+ XCTAssertTrue(navigation.topViewController == controller)
+ XCTAssertTrue(nav == navigation)
+ XCTAssertTrue(embed.child == controller)
+ }
+
+ func testPresenterUtilityEmbedInCustomTabBar() throws {
+ let controller = AppRouterPresenterAdditionalController()
+ let tabBar = UITabBarController()
+ let presenter = controller.presenter().embedInTabBar(tabBar)
+ let embed = try presenter.provideEmbeddedSourceController()
+ guard let tab = embed.parent as? UITabBarController else { return XCTFail() }
+ XCTAssertTrue(tab.viewControllers?.last == controller)
+ XCTAssertTrue(tab == tabBar)
+ XCTAssertTrue(embed.child == controller)
+ }
+
+ func testPresenterUtilityEmbedInCustomProvider() {
+ let controller = AppRouterPresenterAdditionalController()
+ XCTAssertTrue(try controller.presenter().embedIn({ $0 }).provideEmbeddedSourceController().parent == controller)
+ }
+
+ func testPresenterUtilityCustom() throws {
let customPresenter = AppRouterPresenterAdditionalController.presenter()
.fromStoryboard("AppRouterPresenterControllers", initial: false)
.embedIn({ self.navController?.viewControllers = [$0]; return self.navController })
.configure({ $0.initialized = true })
- guard let embeddedController = customPresenter.provideEmbeddedSourceController() as? AppRouterPresenterNavigationController else { return XCTFail() }
+ guard let embeddedController = try customPresenter.provideEmbeddedSourceController().parent as? AppRouterPresenterNavigationController else { return XCTFail() }
XCTAssertTrue(embeddedController == navController)
XCTAssertTrue((embeddedController.topViewController as? AppRouterPresenterAdditionalController)?.initialized == true)
- XCTAssertNil(AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterInstantiationsTests", initial: true).provideEmbeddedSourceController())
+ XCTAssertNil(try? AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterInstantiationsTests", initial: true).provideEmbeddedSourceController())
}
- func testPresenterPresent() {
+ func testPresenterPresent() throws {
XCTAssertTrue(AppRouter.topViewController == baseController)
- guard let presented = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).configure({ $0.initialized = true }).present(animated: false) else { return XCTFail() }
+ let presented = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).configure({ $0.initialized = true }).present(animated: false)
XCTAssertTrue(AppRouter.topViewController == presented)
XCTAssertTrue(baseController?.presentedViewController == presented)
- XCTAssertTrue(presented.initialized)
+ XCTAssertTrue(presented?.initialized == true)
XCTAssertNil(AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterInstantiationsTests", initial: true).present())
}
- func testPresenterPush() {
+ func testPresenterPush() throws {
XCTAssertTrue(AppRouter.topViewController == baseController)
- guard let pushed = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).configure({ $0.initialized = true }).push(animated: false) else { return XCTFail() }
+ let pushed = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).configure({ $0.initialized = true }).push(animated: false)
XCTAssertTrue(AppRouter.topViewController == pushed)
- XCTAssertTrue(pushed.navigationController == navController)
- XCTAssertTrue(pushed.initialized)
+ XCTAssertTrue(pushed?.navigationController == navController)
+ XCTAssertTrue(pushed?.initialized == true)
XCTAssertNil(AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterInstantiationsTests").push())
}
+ func testPresenterSetAsRoot() throws {
+ XCTAssertTrue(AppRouter.topViewController == baseController)
+ let presented = AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterPresenterControllers", initial: false).configure({ $0.initialized = true }).setAsRoot(animation: .none)
+ XCTAssertTrue(AppRouter.rootViewController == presented)
+ XCTAssertTrue(presented?.initialized == true)
+ XCTAssertNil(AppRouterPresenterAdditionalController.presenter().fromStoryboard("AppRouterInstantiationsTests", initial: true).setAsRoot())
+ }
+
func testPresenterOnInstance() {
let controller = UIViewController()
- XCTAssertTrue(controller.presenter().provideSourceController() == controller)
+ XCTAssertTrue(try controller.presenter().provideSourceController() == controller)
}
}