diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..47f3aa0 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,78 @@ +{ + "originHash" : "896673328595425ab5a6021a854492efa4b4361d3d04341f9b0a9cdf08f6bac8", + "pins" : [ + { + "identity" : "merge", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vmanot/Merge.git", + "state" : { + "branch" : "master", + "revision" : "2f04b1322cf695fb5e464bcf75fc0c1cfaef0ef3" + } + }, + { + "identity" : "swallow", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vmanot/Swallow.git", + "state" : { + "branch" : "master", + "revision" : "444e02e89e0336f66a7d4e36abd93f231436f55c" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-subprocess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/preternatural-fork/swift-subprocess.git", + "state" : { + "branch" : "release/0.2.1", + "revision" : "5f6ae03e819a255de6315aa1c89d28fddd0c7ffe" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-precompiled/swift-syntax", + "state" : { + "branch" : "release/6.1", + "revision" : "fc197a24fb2e77609fbe3d94624e36f84d758099" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system", + "state" : { + "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", + "version" : "1.6.3" + } + }, + { + "identity" : "swiftuix", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwiftUIX/SwiftUIX.git", + "state" : { + "branch" : "master", + "revision" : "9e6dc79e584dd36624254eea4c624bfce98bad4b" + } + } + ], + "version" : 3 +} diff --git a/Sources/Intermodular/Extensions/UIKit/UINavigationController++.swift b/Sources/Intermodular/Extensions/UIKit/UINavigationController++.swift index 34f51a3..228901d 100644 --- a/Sources/Intermodular/Extensions/UIKit/UINavigationController++.swift +++ b/Sources/Intermodular/Extensions/UIKit/UINavigationController++.swift @@ -20,6 +20,19 @@ extension UINavigationController { CATransaction.commit() } + func setViewControllers( + _ viewControllers: [UIViewController], + animated: Bool, + completion: (() -> Void)? + ) { + CATransaction.begin() + CATransaction.setCompletionBlock(completion) + + setViewControllers(viewControllers, animated: animated) + + CATransaction.commit() + } + func popViewController( animated: Bool, completion: (() -> Void)? diff --git a/Sources/Intermodular/Helpers/AppKit or UIKit/UIViewController+ViewTransition.swift b/Sources/Intermodular/Helpers/AppKit or UIKit/UIViewController+ViewTransition.swift index 2690195..26cc92f 100644 --- a/Sources/Intermodular/Helpers/AppKit or UIKit/UIViewController+ViewTransition.swift +++ b/Sources/Intermodular/Helpers/AppKit or UIKit/UIViewController+ViewTransition.swift @@ -11,29 +11,28 @@ import SwiftUIX extension UIViewController { public func trigger( _ transition: ViewTransition, - animated: Bool, completion: @escaping () -> () ) throws { switch transition.finalize() { case .present(let view): do { - presentOnTop(view, named: transition.payloadViewName, animated: animated) { + presentOnTop(view, named: transition.payloadViewName, animated: transition.animated) { completion() } } case .replace(let view): do { if let viewController = topmostPresentedViewController?.presentingViewController { - viewController.dismiss(animated: animated) { + viewController.dismiss(animated: transition.animated) { viewController.presentOnTop( view, named: transition.payloadViewName, - animated: animated + animated: transition.animated ) { completion() } } } else { - presentOnTop(view, named: transition.payloadViewName, animated: animated) { + presentOnTop(view, named: transition.payloadViewName, animated: transition.animated) { completion() } } @@ -44,7 +43,7 @@ extension UIViewController { throw ViewTransition.Error.nothingToDismiss } - dismiss(animated: animated) { + dismiss(animated: transition.animated) { completion() } } @@ -62,7 +61,7 @@ extension UIViewController { navigationController.pushViewController( view._toAppKitOrUIKitViewController(), - animated: animated + animated: transition.animated ) { completion() } @@ -72,12 +71,12 @@ extension UIViewController { if let navigationController = nearestNavigationController { navigationController.pushViewController( view._toAppKitOrUIKitViewController(), - animated: animated + animated: transition.animated ) { completion() } } else { - presentOnTop(view, named: transition.payloadViewName, animated: animated) { + presentOnTop(view, named: transition.payloadViewName, animated: transition.animated) { completion() } } @@ -88,7 +87,7 @@ extension UIViewController { throw ViewTransition.Error.navigationControllerMissing } - viewController.popViewController(animated: animated) { + viewController.popViewController(animated: transition.animated) { completion() } } @@ -98,14 +97,14 @@ extension UIViewController { throw ViewTransition.Error.navigationControllerMissing } - viewController.popToRootViewController(animated: animated) { + viewController.popToRootViewController(animated: transition.animated) { completion() } } case .popOrDismiss: do { if let navigationController = nearestNavigationController, navigationController.viewControllers.count > 1 { - navigationController.popViewController(animated: animated) { + navigationController.popViewController(animated: transition.animated) { completion() } } else { @@ -113,7 +112,7 @@ extension UIViewController { throw ViewTransition.Error.nothingToDismiss } - dismiss(animated: animated) { + dismiss(animated: transition.animated) { completion() } } @@ -121,7 +120,7 @@ extension UIViewController { case .popToRootOrDismiss: do { if let navigationController = nearestNavigationController, navigationController.viewControllers.count > 1 { - navigationController.popToRootViewController(animated: animated) { + navigationController.popToRootViewController(animated: transition.animated) { completion() } } else { @@ -129,7 +128,7 @@ extension UIViewController { throw ViewTransition.Error.nothingToDismiss } - dismiss(animated: animated) { + dismiss(animated: transition.animated) { completion() } } @@ -151,7 +150,7 @@ extension UIViewController { case .set(let view, _): do { if let viewController = nearestNavigationController { - viewController.setViewControllers([view._toAppKitOrUIKitViewController()], animated: animated) + viewController.setViewControllers([view._toAppKitOrUIKitViewController()], animated: transition.animated) completion() } else if let window = self.view.window, window.rootViewController === self { @@ -163,13 +162,26 @@ extension UIViewController { completion() } else if topmostPresentedViewController != nil { - dismiss(animated: animated) { - self.presentOnTop(view, named: transition.payloadViewName, animated: animated) { + dismiss(animated: transition.animated) { + self.presentOnTop(view, named: transition.payloadViewName, animated: transition.animated) { completion() } } } } + + case .setMany(let views): do { + guard let navigationController = nearestNavigationController else { + throw ViewTransition.Error.navigationControllerMissing + } + + navigationController.setViewControllers( + views.map { $0._toAppKitOrUIKitViewController() }, + animated: transition.animated + ) { + completion() + } + } case .linear(var transitions): do { guard !transitions.isEmpty else { @@ -178,11 +190,12 @@ extension UIViewController { var _error: Error? - let firstTransition = transitions.removeFirst() + var firstTransition = transitions.removeFirst() + firstTransition.animated = transition.animated && firstTransition.animated - try trigger(firstTransition, animated: animated) { + try trigger(firstTransition) { do { - try self.trigger(.linear(transitions), animated: animated) { + try self.trigger(.linear(transitions)) { completion() } } catch { @@ -218,18 +231,17 @@ extension ViewTransition { @_transparent func triggerPublisher( in controller: UIViewController, - animated: Bool, coordinator: VC ) -> AnyPublisher { let transition = merge(coordinator: coordinator) if case .custom(let trigger) = transition.finalize() { - return trigger() + return trigger(animated) } return Future { attemptToFulfill in do { - try controller.trigger(transition, animated: animated) { + try controller.trigger(transition) { attemptToFulfill(.success(transition)) } } catch { diff --git a/Sources/Intermodular/Helpers/AppKit or UIKit/UIWindow+ViewTransition.swift b/Sources/Intermodular/Helpers/AppKit or UIKit/UIWindow+ViewTransition.swift index c4a3341..17e2d88 100644 --- a/Sources/Intermodular/Helpers/AppKit or UIKit/UIWindow+ViewTransition.swift +++ b/Sources/Intermodular/Helpers/AppKit or UIKit/UIWindow+ViewTransition.swift @@ -13,10 +13,9 @@ extension ViewTransition { coordinator: Coordinator ) -> AnyPublisher { let transition = merge(coordinator: coordinator) - let animated = transition.animated if case .custom(let trigger) = transition.finalize() { - return trigger() + return trigger(animated) } return Future { attemptToFulfill in @@ -54,7 +53,7 @@ extension ViewTransition { } default: do { do { - try window.rootViewController.unwrap().trigger(transition, animated: animated) { + try window.rootViewController.unwrap().trigger(transition) { attemptToFulfill(.success(self)) } } catch { diff --git a/Sources/Intramodular/Bridging/AppKitOrUIKitViewControllerCoordinatorType.swift b/Sources/Intramodular/Bridging/AppKitOrUIKitViewControllerCoordinatorType.swift index 9deab64..3317883 100644 --- a/Sources/Intramodular/Bridging/AppKitOrUIKitViewControllerCoordinatorType.swift +++ b/Sources/Intramodular/Bridging/AppKitOrUIKitViewControllerCoordinatorType.swift @@ -53,16 +53,19 @@ open class UIViewControllerCoordinator: _AppKitOrUIKitViewCoordinatorBase fatalError() } - public override func triggerPublisher(for route: Route) -> AnyPublisher { + public override func triggerPublisher(for route: Route, animated: Bool = true) -> AnyPublisher { guard let rootViewController = rootViewController else { runtimeIssue("Could not resolve a root view controller.") return .failure(TriggerError.rootViewControllerMissing) } - return transition(for: route) + var transition = transition(for: route) + transition.animated = animated + + return transition .environment(environmentInsertions) - .triggerPublisher(in: rootViewController, animated: true, coordinator: self) + .triggerPublisher(in: rootViewController, coordinator: self) .handleOutput { [weak self] _ in self?.updateAllChildren() } diff --git a/Sources/Intramodular/Bridging/AppKitOrUIKitWindowCoordinatorType.swift b/Sources/Intramodular/Bridging/AppKitOrUIKitWindowCoordinatorType.swift index 4494ae1..f0275fa 100644 --- a/Sources/Intramodular/Bridging/AppKitOrUIKitWindowCoordinatorType.swift +++ b/Sources/Intramodular/Bridging/AppKitOrUIKitWindowCoordinatorType.swift @@ -56,12 +56,16 @@ open class AppKitOrUIKitWindowCoordinator: _AppKitOrUIKitViewCoordinatorB @discardableResult override public func triggerPublisher( - for route: Route + for route: Route, + animated: Bool = true ) -> AnyPublisher { do { let window = try self.window.unwrap() - return transition(for: route) + var transition = transition(for: route) + transition.animated = animated + + return transition .environment(environmentInsertions) .triggerPublisher(in: window, coordinator: self) .handleOutput { [weak self] _ in @@ -80,9 +84,10 @@ open class AppKitOrUIKitWindowCoordinator: _AppKitOrUIKitViewCoordinatorB @discardableResult override public func trigger( - _ route: Route + _ route: Route, + animated: Bool = true ) -> AnyPublisher { - super.trigger(route) + super.trigger(route, animated: animated) } } diff --git a/Sources/Intramodular/Bridging/_AppKitOrUIKitViewCoordinatorBase.swift b/Sources/Intramodular/Bridging/_AppKitOrUIKitViewCoordinatorBase.swift index 5bf694d..5ecc4aa 100644 --- a/Sources/Intramodular/Bridging/_AppKitOrUIKitViewCoordinatorBase.swift +++ b/Sources/Intramodular/Bridging/_AppKitOrUIKitViewCoordinatorBase.swift @@ -114,13 +114,13 @@ open class _AppKitOrUIKitViewCoordinatorBase: _opaque_AppKitOrUIKitViewCo fatalError() } - public func triggerPublisher(for route: Route) -> AnyPublisher { + public func triggerPublisher(for route: Route, animated: Bool) -> AnyPublisher { Empty().eraseToAnyPublisher() } @discardableResult - public func trigger(_ route: Route) -> AnyPublisher { - let publisher = triggerPublisher(for: route) + public func trigger(_ route: Route, animated: Bool = true) -> AnyPublisher { + let publisher = triggerPublisher(for: route, animated: animated) let result = PassthroughSubject() publisher.subscribe(result, in: cancellables) diff --git a/Sources/Intramodular/Core/AnyViewCoordinator.swift b/Sources/Intramodular/Core/AnyViewCoordinator.swift index ef1db75..47caf10 100644 --- a/Sources/Intramodular/Core/AnyViewCoordinator.swift +++ b/Sources/Intramodular/Core/AnyViewCoordinator.swift @@ -23,8 +23,8 @@ public final class AnyViewCoordinator: _opaque_AnyViewCoordinator, ViewCo } private let transitionImpl: (Route) -> ViewTransition - private let triggerPublisherImpl: (Route) -> AnyPublisher - private let triggerImpl: @MainActor (Route) -> AnyPublisher + private let triggerPublisherImpl: (Route, Bool) -> AnyPublisher + private let triggerImpl: @MainActor (Route, Bool) -> AnyPublisher public init( _ coordinator: VC @@ -41,14 +41,14 @@ public final class AnyViewCoordinator: _opaque_AnyViewCoordinator, ViewCo } @discardableResult - public func triggerPublisher(for route: Route) -> AnyPublisher { - triggerPublisherImpl(route) + public func triggerPublisher(for route: Route, animated: Bool = true) -> AnyPublisher { + triggerPublisherImpl(route, animated) } @discardableResult @MainActor - public func trigger(_ route: Route) -> AnyPublisher { - triggerImpl(route) + public func trigger(_ route: Route, animated: Bool = true) -> AnyPublisher { + triggerImpl(route, animated) } #if os(iOS) || os(macOS) || os(tvOS) diff --git a/Sources/Intramodular/Core/ViewCoordinator.swift b/Sources/Intramodular/Core/ViewCoordinator.swift index a7af8e0..12619e3 100644 --- a/Sources/Intramodular/Core/ViewCoordinator.swift +++ b/Sources/Intramodular/Core/ViewCoordinator.swift @@ -11,11 +11,11 @@ public protocol ViewCoordinator: EnvironmentPropagator, ObservableObject { typealias Transition = ViewTransition - func triggerPublisher(for _: Route) -> AnyPublisher + func triggerPublisher(for _: Route, animated: Bool) -> AnyPublisher @discardableResult @MainActor - func trigger(_: Route) -> AnyPublisher + func trigger(_: Route, animated: Bool) -> AnyPublisher func transition(for: Route) -> Transition } diff --git a/Sources/Intramodular/Transition/ViewTransition.Payload.swift b/Sources/Intramodular/Transition/ViewTransition.Payload.swift index 522c986..60ebd3b 100644 --- a/Sources/Intramodular/Transition/ViewTransition.Payload.swift +++ b/Sources/Intramodular/Transition/ViewTransition.Payload.swift @@ -51,11 +51,12 @@ extension ViewTransition { case popToRootOrDismiss case set(AnyPresentationView, transition: _WindowSetTransition?) + case setMany([AnyPresentationView]) case setRoot(AnyPresentationView) case linear([ViewTransition]) - case custom(() -> AnyPublisher) + case custom((_ animated: Bool) -> AnyPublisher) case none } @@ -88,6 +89,8 @@ extension ViewTransition.Payload { return nil case .set(let view, _): return view + case .setMany: + return nil case .setRoot(let view): return view case .linear: @@ -125,6 +128,8 @@ extension ViewTransition.Payload { break case .set(_, let transition): self = .set(newValue, transition: transition) + case .setMany: + break case .setRoot: self = .setRoot(newValue) case .linear: diff --git a/Sources/Intramodular/Transition/ViewTransition.swift b/Sources/Intramodular/Transition/ViewTransition.swift index d18a990..7ddfc4f 100644 --- a/Sources/Intramodular/Transition/ViewTransition.swift +++ b/Sources/Intramodular/Transition/ViewTransition.swift @@ -18,7 +18,7 @@ public struct ViewTransition: ViewTransitionContext { private var payload: Payload - var animated: Bool = true + public var animated: Bool = true var payloadViewName: AnyHashable? var payloadViewType: Any.Type? var environmentInsertions: EnvironmentInsertions @@ -82,6 +82,8 @@ extension ViewTransition { return nil case .set: return nil + case .setMany: + return nil case .setRoot: return nil case .linear: @@ -121,6 +123,8 @@ extension ViewTransition: CustomStringConvertible { return "Pop to root or dismiss" case .set: return "Set" + case .setMany: + return "Set Many" case .setRoot: return "Set root" case .linear: @@ -248,6 +252,10 @@ extension ViewTransition { .init(payload: ViewTransition.Payload.setRoot, view: view) } + public static func setMany(_ views: [AnyPresentationView]) -> Self { + .init(payload: ViewTransition.Payload.setMany(views)) + } + public static func linear(_ transitions: [ViewTransition]) -> Self { .init(payload: .linear(transitions)) } @@ -257,30 +265,30 @@ extension ViewTransition { } internal static func custom( - _ body: @escaping () -> AnyPublisher + _ body: @escaping (Bool) -> AnyPublisher ) -> ViewTransition { .init(payload: .custom(body)) } @available(*, deprecated, renamed: "custom") internal static func dynamic( - _ body: @escaping () -> Void + _ body: @escaping (Bool) -> Void ) -> ViewTransition { .custom(body) } public static func custom( - @_implicitSelfCapture _ body: @escaping () -> Void + @_implicitSelfCapture _ body: @escaping (Bool) -> Void ) -> ViewTransition { // FIXME: Set a correct view transition context. struct CustomViewTransitionContext: ViewTransitionContext { } - return .custom { () -> AnyPublisher in + return .custom { (animated: Bool) -> AnyPublisher in Deferred { Future { attemptToFulfill in - body() + body(animated) attemptToFulfill(.success(CustomViewTransitionContext())) }