summaryrefslogtreecommitdiffhomepage
path: root/ios/Routing
diff options
context:
space:
mode:
Diffstat (limited to 'ios/Routing')
-rw-r--r--ios/Routing/Coordinator.swift33
-rw-r--r--ios/Routing/Router/AppRouteProtocol.swift4
-rw-r--r--ios/Routing/Router/ApplicationRouter.swift27
-rw-r--r--ios/Routing/Router/ApplicationRouterDelegate.swift2
-rw-r--r--ios/Routing/Router/ApplicationRouterTypes.swift53
5 files changed, 105 insertions, 14 deletions
diff --git a/ios/Routing/Coordinator.swift b/ios/Routing/Coordinator.swift
index 217a951aef..46e343e648 100644
--- a/ios/Routing/Coordinator.swift
+++ b/ios/Routing/Coordinator.swift
@@ -84,13 +84,23 @@ open class Coordinator: NSObject {
*/
public protocol Presentable: Coordinator {
/**
- View controller that is presented modally. It's expected it to be the top-most view controller
+ View controller that is presented modally. It's expected it to be the topmost view controller
managed by coordinator.
*/
var presentedViewController: UIViewController { get }
}
/**
+ Protocol describing `Presentable` coordinators that can be popped from a navigation stack.
+ */
+public protocol Poppable: Presentable {
+ func popFromNavigationStack(
+ animated: Bool,
+ completion: (() -> Void)?
+ )
+}
+
+/**
Protocol describing coordinators that provide modal presentation context.
*/
public protocol Presenting: Coordinator {
@@ -100,6 +110,15 @@ public protocol Presenting: Coordinator {
var presentationContext: UIViewController { get }
}
+extension Presenting where Self: Presentable {
+ /**
+ View controller providing modal presentation context.
+ */
+ public var presentationContext: UIViewController {
+ return presentedViewController
+ }
+}
+
extension Presenting {
/**
Present child coordinator.
@@ -134,12 +153,22 @@ extension Presenting {
addChild(child)
- presentationContext.present(
+ topmostPresentationContext(from: presentationContext).present(
child.presentedViewController,
animated: animated,
completion: completion
)
}
+
+ private func topmostPresentationContext(from: UIViewController) -> UIViewController {
+ var context = presentationContext
+
+ while let childContext = context.presentedViewController, context != childContext {
+ context = childContext
+ }
+
+ return context
+ }
}
extension Presentable {
diff --git a/ios/Routing/Router/AppRouteProtocol.swift b/ios/Routing/Router/AppRouteProtocol.swift
index 1843183958..85fbd53028 100644
--- a/ios/Routing/Router/AppRouteProtocol.swift
+++ b/ios/Routing/Router/AppRouteProtocol.swift
@@ -35,6 +35,10 @@ extension AppRouteGroupProtocol {
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.modalLevel < rhs.modalLevel
}
+
+ public static func <= (lhs: Self, rhs: Self) -> Bool {
+ lhs.modalLevel <= rhs.modalLevel
+ }
}
/**
diff --git a/ios/Routing/Router/ApplicationRouter.swift b/ios/Routing/Router/ApplicationRouter.swift
index 2de0cbc3ff..d565875143 100644
--- a/ios/Routing/Router/ApplicationRouter.swift
+++ b/ios/Routing/Router/ApplicationRouter.swift
@@ -53,10 +53,11 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
/**
Enqueue route for presetnation.
*/
- public func present(_ route: RouteType, animated: Bool = true) {
+ public func present(_ route: RouteType, animated: Bool = true, metadata: Any? = nil) {
enqueue(PendingRoute(
operation: .present(route),
- animated: animated
+ animated: animated,
+ metadata: metadata
))
}
@@ -81,7 +82,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
}
private func enqueue(_ pendingRoute: PendingRoute<RouteType>) {
- logger.debug("Enqueue \(pendingRoute.operation).")
+ logger.debug("\(pendingRoute.operation).")
pendingRoutes.append(pendingRoute)
@@ -93,6 +94,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
private func presentRoute(
_ route: RouteType,
animated: Bool,
+ metadata: Any?,
completion: @escaping (PendingPresentationResult) -> Void
) {
/**
@@ -117,7 +119,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
}
/**
- Drop duplicate routes.
+ Drop duplicate exclusive routes.
*/
if route.isExclusive, modalStack.contains(route.routeGroup) {
completion(.drop)
@@ -135,8 +137,15 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
/**
Check if route can be presented above the last route in the modal stack.
*/
- if let lastRouteGroup = modalStack.last, route.routeGroup.isModal,
- (lastRouteGroup > route.routeGroup) || (route.isExclusive && lastRouteGroup == route.routeGroup) {
+ if let
+ // Get current modal route.
+ lastRouteGroup = modalStack.last,
+ // Check if incoming route is modal.
+ route.routeGroup.isModal,
+ // Check whether incoming route can be presented on top of current.
+ (lastRouteGroup > route.routeGroup) ||
+ // OR, check whether incoming exclusive route can be presented on top of current.
+ (lastRouteGroup >= route.routeGroup && route.isExclusive) {
completion(.blockedByModalContext)
return
}
@@ -145,7 +154,9 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
Consult with delegate whether the route should still be presented.
*/
if delegate.applicationRouter(self, shouldPresent: route) {
- delegate.applicationRouter(self, route: route, animated: animated) { coordinator in
+ let context = RoutePresentationContext(route: route, isAnimated: animated, metadata: metadata)
+
+ delegate.applicationRouter(self, presentWithContext: context, animated: animated) { coordinator in
/*
Synchronize router when modal controllers are removed by swipe.
*/
@@ -276,7 +287,7 @@ public final class ApplicationRouter<RouteType: AppRouteProtocol> {
switch pendingRoute.operation {
case let .present(route):
- presentRoute(route, animated: pendingRoute.animated) { result in
+ presentRoute(route, animated: pendingRoute.animated, metadata: pendingRoute.metadata) { result in
switch result {
case .success, .drop:
self.finishPendingRoute(pendingRoute)
diff --git a/ios/Routing/Router/ApplicationRouterDelegate.swift b/ios/Routing/Router/ApplicationRouterDelegate.swift
index ecccb15415..a98870d303 100644
--- a/ios/Routing/Router/ApplicationRouterDelegate.swift
+++ b/ios/Routing/Router/ApplicationRouterDelegate.swift
@@ -19,7 +19,7 @@ public protocol ApplicationRouterDelegate<RouteType>: AnyObject {
*/
func applicationRouter(
_ router: ApplicationRouter<RouteType>,
- route: RouteType,
+ presentWithContext context: RoutePresentationContext<RouteType>,
animated: Bool,
completion: @escaping (Coordinator) -> Void
)
diff --git a/ios/Routing/Router/ApplicationRouterTypes.swift b/ios/Routing/Router/ApplicationRouterTypes.swift
index 1dfd128c0a..7be142adaf 100644
--- a/ios/Routing/Router/ApplicationRouterTypes.swift
+++ b/ios/Routing/Router/ApplicationRouterTypes.swift
@@ -11,9 +11,16 @@ import Foundation
/**
Struct describing a routing request for presentation or dismissal.
*/
-struct PendingRoute<RouteType: AppRouteProtocol>: Equatable {
+struct PendingRoute<RouteType: AppRouteProtocol> {
var operation: RouteOperation<RouteType>
var animated: Bool
+ var metadata: Any?
+}
+
+extension PendingRoute: Equatable {
+ static func == (lhs: PendingRoute<RouteType>, rhs: PendingRoute<RouteType>) -> Bool {
+ lhs.operation == rhs.operation
+ }
}
/**
@@ -69,7 +76,7 @@ enum PendingDismissalResult {
/**
Enum describing operation over the route.
*/
-enum RouteOperation<RouteType: AppRouteProtocol>: Equatable {
+enum RouteOperation<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConvertible {
/**
Present route.
*/
@@ -91,12 +98,23 @@ enum RouteOperation<RouteType: AppRouteProtocol>: Equatable {
return dismissMatch.routeGroup
}
}
+
+ var debugDescription: String {
+ let action: String
+ switch self {
+ case let .present(routeType):
+ action = "Presenting .\(routeType)"
+ case let .dismiss(match):
+ action = "Dismissing .\(match)"
+ }
+ return "\(action)"
+ }
}
/**
Enum type describing a single route or a group of routes requested to be dismissed.
*/
-enum DismissMatch<RouteType: AppRouteProtocol>: Equatable {
+enum DismissMatch<RouteType: AppRouteProtocol>: Equatable, CustomDebugStringConvertible {
case group(RouteType.RouteGroupType)
case singleRoute(RouteType)
@@ -111,6 +129,15 @@ enum DismissMatch<RouteType: AppRouteProtocol>: Equatable {
return route.routeGroup
}
}
+
+ var debugDescription: String {
+ switch self {
+ case let .group(group):
+ return "\(group)"
+ case let .singleRoute(route):
+ return "\(route)"
+ }
+ }
}
/**
@@ -142,6 +169,26 @@ public struct RouteDismissalContext<RouteType: AppRouteProtocol> {
}
/**
+ Struct holding information used by delegate to perform presentation of a specific route.
+ */
+public struct RoutePresentationContext<RouteType: AppRouteProtocol> {
+ /**
+ Route that's being presented.
+ */
+ public var route: RouteType
+
+ /**
+ Whether transition is animated.
+ */
+ public var isAnimated: Bool
+
+ /**
+ Metadata associated with the route.
+ */
+ public var metadata: Any?
+}
+
+/**
Struct holding information used by delegate to perform sub-navigation of the route in subject.
*/
public struct RouteSubnavigationContext<RouteType: AppRouteProtocol> {