🎌 Powerful navigation library for iOS based on the coordinator pattern

Overview

Build Status CocoaPods Compatible Carthage Compatible Documentation Platform License

⚠️ We have recently released XCoordinator 2.0. Make sure to read this section before migrating. In general, please replace all AnyRouter by either UnownedRouter (in viewControllers, viewModels or references to parent coordinators) or StrongRouter in your AppDelegate or for references to child coordinators. In addition to that, the rootViewController is now injected into the initializer instead of being created in the Coordinator.generateRootViewController method.

“How does an app transition from one view controller to another?”. This question is common and puzzling regarding iOS development. There are many answers, as every architecture has different implementation variations. Some do it from within the implementation of a view controller, while some use a router/coordinator, an object connecting view models.

To better answer the question, we are building XCoordinator, a navigation framework based on the Coordinator pattern. It's especially useful for implementing MVVM-C, Model-View-ViewModel-Coordinator:

🏃‍♂️ Getting started

Create an enum with all of the navigation paths for a particular flow, i.e. a group of closely connected scenes. (It is up to you when to create a Route/Coordinator. As our rule of thumb, create a new Route/Coordinator whenever a new root view controller, e.g. a new navigation controller or a tab bar controller, is needed.).

Whereas the Route describes which routes can be triggered in a flow, the Coordinator is responsible for the preparation of transitions based on routes being triggered. We could, therefore, prepare multiple coordinators for the same route, which differ in which transitions are executed for each route.

In the following example, we create the UserListRoute enum to define triggers of a flow of our application. UserListRoute offers routes to open the home screen, display a list of users, to open a specific user and to log out. The UserListCoordinator is implemented to prepare transitions for the triggered routes. When a UserListCoordinator is shown, it triggers the .home route to display a HomeViewController.

enum UserListRoute: Route {
    case home
    case users
    case user(String)
    case registerUsersPeek(from: Container)
    case logout
}

class UserListCoordinator: NavigationCoordinator<UserListRoute> {
    init() {
        super.init(initialRoute: .home)
    }

    override func prepareTransition(for route: UserListRoute) -> NavigationTransition {
        switch route {
        case .home:
            let viewController = HomeViewController.instantiateFromNib()
            let viewModel = HomeViewModelImpl(router: unownedRouter)
            viewController.bind(to: viewModel)
            return .push(viewController)
        case .users:
            let viewController = UsersViewController.instantiateFromNib()
            let viewModel = UsersViewModelImpl(router: unownedRouter)
            viewController.bind(to: viewModel)
            return .push(viewController, animation: .interactiveFade)
        case .user(let username):
            let coordinator = UserCoordinator(user: username)
            return .present(coordinator, animation: .default)
        case .registerUsersPeek(let source):
            return registerPeek(for: source, route: .users)
        case .logout:
            return .dismiss()
        }
    }
}

Routes are triggered from within Coordinators or ViewModels. In the following, we describe how to trigger routes from within a ViewModel. The router of the current flow is injected into the ViewModel.

class HomeViewModel {
    let router: UnownedRouter<HomeRoute>

    init(router: UnownedRouter<HomeRoute>) {
        self.router = router
    }

    /* ... */

    func usersButtonPressed() {
        router.trigger(.users)
    }
}

🏗 Organizing an app's structure with XCoordinator

In general, an app's structure is defined by nesting coordinators and view controllers. You can transition (i.e. push, present, pop, dismiss) to a different coordinator whenever your app changes to a different flow. Within a flow, we transition between viewControllers.

Example: In UserListCoordinator.prepareTransition(for:) we change from the UserListRoute to the UserRoute whenever the UserListRoute.user route is triggered. By dismissing a viewController in UserListRoute.logout, we additionally switch back to the previous flow - in this case the HomeRoute.

To achieve this behavior, every Coordinator has its own rootViewController. This would be a UINavigationController in the case of a NavigationCoordinator, a UITabBarController in the case of a TabBarCoordinator, etc. When transitioning to a Coordinator/Router, this rootViewController is used as the destination view controller.

🏁 Using XCoordinator from App Launch

To use coordinators from the launch of the app, make sure to create the app's window programmatically in AppDelegate.swift (Don't forget to remove Main Storyboard file base name from Info.plist). Then, set the coordinator as the root of the window's view hierarchy in the AppDelegate.didFinishLaunching. Make sure to hold a strong reference to your app's initial coordinator or a strongRouter reference.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    let window: UIWindow! = UIWindow()
    let router = AppCoordinator().strongRouter

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        router.setRoot(for: window)
        return true
    }
}

🤸‍♂️ Extras

For more advanced use, XCoordinator offers many more customization options. We introduce custom animated transitions and deep linking. Furthermore, extensions for use in reactive programming with RxSwift/Combine and options to split up huge routes are described.

🌗 Custom Transitions

Custom animated transitions define presentation and dismissal animations. You can specify Animation objects in prepareTransition(for:) in your coordinator for several common transitions, such as present, dismiss, push and pop. Specifying no animation (nil) results in not overriding previously set animations. Use Animation.default to reset previously set animation to the default animations UIKit offers.

class UsersCoordinator: NavigationCoordinator<UserRoute> {

    /* ... */
    
    override func prepareTransition(for route: UserRoute) -> NavigationTransition {
        switch route {
        case .user(let name):
            let animation = Animation(
                presentationAnimation: YourAwesomePresentationTransitionAnimation(),
                dismissalAnimation: YourAwesomeDismissalTransitionAnimation()
            )
            let viewController = UserViewController.instantiateFromNib()
            let viewModel = UserViewModelImpl(name: name, router: unownedRouter)
            viewController.bind(to: viewModel)
            return .push(viewController, animation: animation)
        /* ... */
        }
    }
}

🛤 Deep Linking

Deep Linking can be used to chain different routes together. In contrast to the .multiple transition, deep linking can identify routers based on previous transitions (e.g. when pushing or presenting a router), which enables chaining of routes of different types. Keep in mind, that you cannot access higher-level routers anymore once you trigger a route on a lower level of the router hierarchy.

class AppCoordinator: NavigationCoordinator<AppRoute> {

    /* ... */

    override func prepareTransition(for route: AppRoute) -> NavigationTransition {
        switch route {
        /* ... */
        case .deep:
            return deepLink(AppRoute.login, AppRoute.home, HomeRoute.news, HomeRoute.dismiss)
        }
    }
}

⚠️ XCoordinator does not check at compile-time, whether a deep link can be executed. Rather it uses assertionFailures to inform about incorrect chaining at runtime, when it cannot find an appriopriate router for a given route. Keep this in mind when changing the structure of your app.

🚏 RedirectionRouter

Let's assume, there is a route type called HugeRoute with more than 10 routes. To decrease coupling, HugeRoute needs to be split up into mutliple route types. As you will discover, many routes in HugeRoute use transitions dependent on a specific rootViewController, such as push, show, pop, etc. If splitting up routes by introducing a new router/coordinator is not an option, XCoordinator has two solutions for you to solve such a case: RedirectionRouter or using multiple coordinators with the same rootViewController (see this section for more information).

A RedirectionRouter can be used to map a new route type onto a generalized ParentRoute. A RedirectionRouter is independent of the TransitionType of its parent router. You can use RedirectionRouter.init(viewController:parent:map:) or subclassing by overriding mapToParentRoute(_:) to create a RedirectionRouter.

The following code example illustrates how a RedirectionRouter is initialized and used.

class ParentCoordinator: NavigationCoordinator<ParentRoute> {
    /* ... */
    
    override func prepareTransition(for route: ParentRoute) -> NavigationTransition {
        switch route {
        /* ... */
        case .child:
            let childCoordinator = ChildCoordinator(parent: unownedRouter)
            return .push(childCoordinator)
        }
    }
}

class ChildCoordinator: RedirectionRouter<ParentRoute, ChildRoute> {
    init(parent: UnownedRouter<ParentRoute>) {
        let viewController = UIViewController() 
        // this viewController is used when performing transitions with the Subcoordinator directly.
        super.init(viewController: viewController, parent: parent, map: nil)
    }
    
    /* ... */
    
    override func mapToParentRoute(for route: ChildRoute) -> ParentRoute {
        // you can map your ChildRoute enum to ParentRoute cases here that will get triggered on the parent router.
    }
}

🚏 Using multiple coordinators with the same rootViewController

With XCoordinator 2.0, we introduce the option to use different coordinators with the same rootViewController. Since you can specify the rootViewController in the initializer of a new coordinator, you can specify an existing coordinator's rootViewController as in the following:

class FirstCoordinator: NavigationCoordinator<FirstRoute> {
    /* ... */
    
    override func prepareTransition(for route: FirstRoute) -> NavigationTransition {
        switch route {
        case .secondCoordinator:
            let secondCoordinator = SecondCoordinator(rootViewController: self.rootViewController)
            addChild(secondCoordinator)
            return .none() 
            // you could also trigger a specific initial route at this point, 
            // such as `.trigger(SecondRoute.initial, on: secondCoordinator)`
        }
    }
}

We suggest to not use initial routes in the initializers of sibling coordinators, but instead using the transition option in the FirstCoordinator instead.

⚠️ If you perform transitions involving a sibling coordinator directly (e.g. pushing a sibling coordinator without overriding its viewController property), your app will most likely crash.

🚀 RxSwift/Combine extensions

Reactive programming can be very useful to keep the state of view and model consistent in a MVVM architecture. Instead of relying on the completion handler of the trigger method available in any Router, you can also use our RxSwift-extension. In the example application, we use Actions (from the Action framework) to trigger routes on certain UI events - e.g. to trigger LoginRoute.home in LoginViewModel, when the login button is tapped.

class LoginViewModelImpl: LoginViewModel, LoginViewModelInput, LoginViewModelOutput {

    private let router: UnownedRouter<AppRoute>

    private lazy var loginAction = CocoaAction { [unowned self] in
        return self.router.rx.trigger(.home)
    }

    /* ... */
}

In addition to the above-mentioned approach, the reactive trigger extension can also be used to sequence different transitions by using the flatMap operator, as can be seen in the following:

let doneWithBothTransitions = 
    router.rx.trigger(.home)
        .flatMap { [unowned self] in self.router.rx.trigger(.news) }
        .map { true }
        .startWith(false)

When using XCoordinator with the Combine extensions, you can use router.publishers.trigger instead of router.rx.trigger.

📚 Documentation & Example app

To get more information about XCoordinator, check out the documentation. Additionally, this repository serves as an example project using a MVVM architecture with XCoordinator.

For a MVC example app, have a look at some presentations we did about the Coordinator pattern and XCoordinator.

👨‍✈️ Why coordinators

  • Separation of responsibilities with the coordinator being the only component knowing anything related to the flow of your application.

  • Reusable Views and ViewModels because they do not contain any navigation logic.

  • Less coupling between components

  • Changeable navigation: Each coordinator is only responsible for one component and does not need to make assumptions about its parent. It can therefore be placed wherever we want to.

The Coordinator by Soroush Khanlou

⁉️ Why XCoordinator

  • Actual navigation code is already written and abstracted away.
  • Clear separation of concerns:
    • Coordinator: Coordinates routing of a set of routes.
    • Route: Describes navigation path.
    • Transition: Describe transition type and animation to new view.
  • Reuse coordinators, routers and transitions in different combinations.
  • Full support for custom transitions/animations.
  • Support for embedding child views / container views.
  • Generic BasicCoordinator classes suitable for many use cases and therefore less need to write your own coordinators.
  • Full support for your own coordinator classes conforming to our Coordinator protocol
    • You can also start with one of the following types to get a head start: NavigationCoordinator, ViewCoordinator, TabBarCoordinator and more.
  • Generic AnyRouter type erasure class encapsulates all types of coordinators and routers supporting the same set of routes. Therefore you can easily replace coordinators.
  • Use of enum for routes gives you autocompletion and type safety to perform only transition to routes supported by the coordinator.

🔩 Components

🎢 Route

Describes possible navigation paths within a flow, a collection of closely related scenes.

👨‍✈️ Coordinator / Router

An object loading views and creating viewModels based on triggered routes. A Coordinator creates and performs transitions to these scenes based on the data transferred via the route. In contrast to the coordinator, a router can be seen as an abstraction from that concept limited to triggering routes. Often, a Router is used to abstract from a specific coordinator in ViewModels.

When to use which Router abstraction

You can create different router abstractions using the unownedRouter, weakRouter or strongRouter properties of your Coordinator. You can decide between the following router abstractions of your coordinator:

  • StrongRouter holds a strong reference to the original coordinator. You can use this to hold child coordinators or to specify a certain router in the AppDelegate.
  • WeakRouter holds a weak reference to the original coordinator. You can use this to hold a coordinator in a viewController or viewModel. It can also be used to keep a reference to a sibling or parent coordinator.
  • UnownedRouter holds an unowned reference to the original coordinator. You can use this to hold a coordinator in a viewController or viewModel. It can also be used to keep a reference to a sibling or parent coordinator.

If you want to know more about the differences on how references can be held, have a look here.

🌗 Transition

Transitions describe the navigation from one view to another. Transitions are available based on the type of the root view controller in use. Example: Whereas ViewTransition only supports basic transitions that every root view controller supports, NavigationTransition adds navigation controller specific transitions.

The available transition types include:

  • present presents a view controller on top of the view hierarchy - use presentOnRoot in case you want to present from the root view controller
  • embed embeds a view controller into a container view
  • dismiss dismisses the top most presented view controller - use dismissToRoot to call dismiss on the root view controller
  • none does nothing, may be used to ignore routes or for testing purposes
  • push pushes a view controller to the navigation stack (only in NavigationTransition)
  • pop pops the top view controller from the navigation stack (only in NavigationTransition)
  • popToRoot pops all the view controllers on the navigation stack except the root view controller (only in NavigationTransition)

XCoordinator additionally supports common transitions for UITabBarController, UISplitViewController and UIPageViewController root view controllers.

🛠 Installation

CocoaPods

To integrate XCoordinator into your Xcode project using CocoaPods, add this to your Podfile:

pod 'XCoordinator', '~> 2.0'

To use the RxSwift extensions, add this to your Podfile:

pod 'XCoordinator/RxSwift', '~> 2.0'

To use the Combine extensions, add this to your Podfile:

pod 'XCoordinator/Combine', '~> 2.0'

Carthage

To integrate XCoordinator into your Xcode project using Carthage, add this to your Cartfile:

github "quickbirdstudios/XCoordinator" ~> 2.0

Then run carthage update.

If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained over at Carthage.

Swift Package Manager

See this WWDC presentation about more information how to adopt Swift packages in your app.

Specify https://github.com/quickbirdstudios/XCoordinator.git as the XCoordinator package link. You can then decide between three different frameworks, i.e. XCoordinator, XCoordinatorRx and XCoordinatorCombine. While XCoordinator contains the main framework, you can choose XCoordinatorRx or XCoordinatorCombine to get RxSwift or Combine extensions as well.

Manually

If you prefer not to use any of the dependency managers, you can integrate XCoordinator into your project manually, by downloading the source code and placing the files on your project directory.

👤 Author

This framework is created with ❤️ by QuickBird Studios.

To get more information on XCoordinator check out our blog post.

❤️ Contributing

Open an issue if you need help, if you found a bug, or if you want to discuss a feature request. If you feel like having a chat about XCoordinator with the developers and other users, join our Slack Workspace.

Open a PR if you want to make changes to XCoordinator.

📃 License

XCoordinator is released under an MIT license. See License.md for more information.

Comments
  • Is it OK to call trigger inside a coordinator?

    Is it OK to call trigger inside a coordinator?

    Hello,

    I don't know how to solve this problem.

    I have 3 coordinators: AppCoordinator LoginCoordinator DashboardCoordinator

    The AppCoordinator is run by the app delegate When running the app, if we have a user in userdefaults (or whatever), we present the dashboard coordinator, otherwise, we present the login coordinator.

    Problem I have is this one: In the login screen, I want to press a button and then that view controller and login coordinator are dismissed, at the same time, I want that the dashboard coordinator is presented. Ideally, I would like to present the dashboard coordinator, and behind that, I would like to dismiss the login controller and release the login coordinator.

    How should I do that? My idea is this:

    enum LoginRoute: Route {
        case login
        case dismiss
    }
    
    final class LoginCoordinator: NavigationCoordinator<LoginRoute> {
    
        weak var appCoordinator: AppCoordinator?
    
        init(appCoordinator: AppCoordinator, initialRoute: RouteType? = nil) {
            self.appCoordinator = appCoordinator
            super.init(initialRoute: initialRoute)
        }
    
        override func prepareTransition(for route: LoginRoute) -> NavigationTransition {
            switch route {
            case.login:
                var vc = LoginViewController.instantiateController()
                let model = LoginViewModel(coordinator: anyRouter)
                vc.bind(to: model)
                return .push(vc)
            case .dismiss:
            	self.appCoordinator.trigger(.dashboard) // This is not working
                return .dismiss()
            }
        }
    }
    

    But it is not working. What is the proper way to do this? Thanks a lot.

    question 
    opened by Ricardo1980 17
  • Help me ,  Thanks so much! 丷丷

    Help me , Thanks so much! 丷丷

    https://github.com/cxxcapf/xcoordinator-issues-demo

    Above is my demo URL

    I have Appcoordinator like this: class Appcoordinator: NavigationCoordinator

    switch route { case .login: let coordinator = LoginCoordinator() let nav = coordinator.rootViewController let tmp = BaseViewController() tmp.addChild(nav) tmp.view.addSubview(nav.view) nav.didMove(toParent: tmp) return .push(tmp) case .initial: let tab = MTabBarCoordinator() return .push(tab) }

    LoginCoordinator --Implementation: `enum LoginRoute: Route { case initial case next(viewmodel: LoginViewModel) }

    class LoginCoordinator: NavigationCoordinator { init() { let nav = BaseNavigationController() nav.navigationBar.backgroundColor = UIColor.white super.init(rootViewController: nav, initialRoute: .initial) }

    override func prepareTransition(for route: LoginRoute) -> NavigationTransition {
        switch route {
        case .next(let model):
            let vc = LoginNextViewController()
            vc.viewmodel = model
            return .push(vc)
        case .initial:
            let first = LoginViewController()
            let model = LoginViewModel(router: unownedRouter)
            first.viewmodel = model
            return .push(first)
        }
    }
    
    deinit {
        print(self)  //this method do not work ???
    }
    

    }`

    The problem now is:LoginRoute. next this method can not work

    question 
    opened by cxxcapf 15
  • Black screen and navigation bar seems for a second after Launch Screen before guide screen

    Black screen and navigation bar seems for a second after Launch Screen before guide screen

    Hi! I have a black screen issue for a second after launch screen. Please let me know what is the problem?

    ScreenDelegate:

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
        
        var window: UIWindow?
        private let router = AppCoordinator().strongRouter
        
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            guard let windowScene = (scene as? UIWindowScene) else {
                return
            }
            window = UIWindow(windowScene: windowScene)
            router.setRoot(for: window!)
            window?.makeKeyAndVisible()
        }
    

    AppCoordinator:

    import UIKit
    import XCoordinator
    
    
    enum AppRoute: Route {
        case firstLaunch //Uygulamaya ilk kez giriş yapılmışsa -> guide ekranı
        case authenticateFirst //Daha önce giriş yapılmış ancak user authenticated değilse -> loginRegister ekranı
        case main //Daha önce giriş yapılmışsa ve kullanıcı authenticated ise -> registerType ekranı
    }
    
    class AppCoordinator: NavigationCoordinator<AppRoute> {
        
        // MARK: Initialization
        init() {
            if MyUserDefaults.firstLaunch {
                super.init(initialRoute: .firstLaunch)
            } else if FirestoreHandler.authUID == nil {
                super.init(initialRoute: .authenticateFirst)
            } else {
                super.init(initialRoute: .main)
            }
        }
        
        // MARK: Overrides
        override func prepareTransition(for route: AppRoute) -> NavigationTransition {
            switch route {
            case .firstLaunch:
                let coordinator = GuideCoordinator()
                return .presentFullScreen(coordinator)
            case .authenticateFirst:
                let coordinator = AuthCoordinator()
                return .presentFullScreen(coordinator)
            case .main:
                let coordinator = AuthCoordinator()
                return .presentFullScreen(coordinator)
            }
        }
     
    }
    

    GuideCoordinator:

    import XCoordinator
    
    enum GuideRoute: Route {
        case initial
        case toLogin
    }
    
    class GuideCoordinator: NavigationCoordinator<GuideRoute> {
        
        
        // MARK: Initialization
        init() {
            super.init(initialRoute: .initial)
        }
        
        // MARK: Overrides
        override func prepareTransition(for route: GuideRoute) -> NavigationTransition {
            switch route {
            case .initial:
                let vc = GuideScreenViewController.instantiate()
                let vm = GuideScreenViewModelImpl(router: unownedRouter)
                vc.bind(to: vm)
                return .push(vc)
            case .toLogin:
                let coordinator = AuthCoordinator()
                coordinator.rootViewController.modalPresentationStyle = .fullScreen
                return .present(coordinator)
            }
        }
        
    }
    
    opened by brcbydr 13
  • How to centralize a flow found in 2 coordinators?

    How to centralize a flow found in 2 coordinators?

    Alternative question: How to implement a controller pushed by different coordinators/routers?

    Context

    I have this flow:

    image

    And this is my code:

    class TabCoordinator: TabBarCoordinator<TabRoute> {
        private let playRouter: StrongRouter<PlayRoute>
        private let learnRouter: StrongRouter<LearnRoute>
        // ...
    }
    
    enum LearnRoute: Route {
        case wordsList
        case word(id: String)
    }
    
    class LearnCoordinator: NavigationCoordinator<LearnRoute> {
        override func prepareTransition(for route: LearnRoute) -> NavigationTransition {
            switch route {
            case .wordsList:
                let vc = WordsListViewController(router: self.unownedRouter)
                return .push(vc)
            case let .word(id):
                let vc = WordViewController(router: /*??*/, wordId: id)
                return .push(vc)
            }
        }
    }
    
    enum PlayRoute: Route {
        case game
        case word(id: String)
    }
    
    class PlayCoordinator: NavigationCoordinator<PlayRoute> {
        override func prepareTransition(for route: PlayRoute) -> NavigationTransition {
            switch route {
            case .game:
                let vc = GameViewController(router: self.unownedRouter)
                return .push(vc)
            case let .word(id):
                let vc = WordViewController(router: /*??*/, wordId: id)
                return .push(vc)
            }
        }
    }
    
    class WordViewController: UIViewController {
        var router: UnownedRouter<??>
    
        func didPressOnButton(id: String) {
            self.router.trigger(.word(id: id))
            // WordViewController can be pushed by either PlayCoord or LearnCoord
            // so how to centralize the router object here?
        }
    }
    

    Question

    My question is related to how XCoordinator should be implemented when different paths of the application share identical subpaths.

    In my case, for WordViewController, should I declare as many routers as WordViewController can handle? For example like this:

    class WordViewController: UIViewController {
        var learnRouter: UnownedRouter<LearnRoute>?
        var playRouter: UnownedRouter<PlayRoute>?
    
        func didPressOnButton(id: String) {
            self.learnRouter?.trigger(.word(id: id))
            self.playRouter?.trigger(.word(id: id))
        }
    }
    
    question 
    opened by Kalzem 11
  • RxSwift 6 compatibility

    RxSwift 6 compatibility

    It seems XCoordinator/RxSwift extension is broken with latest release of RxSwift:

     XCoordinator/RxSwift (~> 2.0) was resolved to 2.0.7, which depends on
          RxSwift (~> 5.0)
    

    Also code of extension doesn't seem to be compile with latest version of RxSwift (I've just tried to copy-paste it to my project):

    extension Router {
    
        /// Use this to access the reactive extensions of `Router` objects.
        public var rx: Reactive<Self> {
            // swiftlint:disable:previous identifier_name
            Reactive(self)
        }
    }
    

    Error message: 'Reactive' requires that 'Self' be a class type

    I am setting new project with RxSwift and wanted to try XCoordinator as API seem to be well designed but it kind of blocks me. Any idea how to fix it?

    opened by pzmudzinski 10
  • Crash at UnownedErased.swift - Line 70

    Crash at UnownedErased.swift - Line 70

    This is the crash log file from Craslytics. I think this is related to XCoordinator library that I am using. Can you please help with this?

    Crashed: com.apple.main-thread
    0  libsystem_kernel.dylib         0x1a94eeefc __pthread_kill + 8
    1  libsystem_pthread.dylib        0x1a940e8b8 pthread_kill + 228
    2  libsystem_c.dylib              0x1a939ea74 abort + 104
    3  libswiftCore.dylib             0x1b702af08 swift_vasprintf(char**, char const*, char*) + 58
    4  libswiftCore.dylib             0x1b702b074 swift::swift_abortDynamicReplacementDisabling() + 54
    5  libswiftCore.dylib             0x1b702c99c swift_unownedRetainStrong + 120
    6  libswiftCore.dylib             0x1b7075e50 swift_unknownObjectUnownedLoadStrong + 36
    7  XCoordinator                   0x1031c3ff8 partial apply for closure #1 in static UnownedErased.createValueClosure<A>(for:erase:) + 70 (UnownedErased.swift:70) //CRASH
    8  XCoordinator                   0x1031c3d30 UnownedErased.wrappedValue.getter + 25 (UnownedErased.swift:25)
    9  XCoordinator                   0x1031c3850 UnownedErased<A>.contextTrigger(_:with:completion:) + 59 (UnownedErased+Router.swift:59)
    10 XCoordinator                   0x1031b87f8 partial apply for closure #1 in Router.trigger(_:with:completion:) + 83 (Router.swift:83)
    11 XCoordinator                   0x1031b882c partial apply for thunk for @callee_guaranteed () -> (@error @owned Error) + 4374726700 (<compiler-generated>:4374726700)
    12 libswiftObjectiveC.dylib       0x1df34ed9c autoreleasepool<A>(invoking:) + 64
    13 XCoordinator                   0x1031b8500 Router.trigger(_:completion:) + 82 (Router.swift:82)
    14 Less                           0x10284fd48 TallyViewModel.openPastDays() + 4364909896
    15 Less                           0x102833ac8 partial apply + 4364794568 (<compiler-generated>:4364794568)
    16 Less                           0x10288d88c thunk for @escaping @callee_guaranteed () -> () + 4365162636 (<compiler-generated>:4365162636)
    17 RxSwift                        0x1030a6964 closure #1 in ObservableType.subscribe(onNext:onError:onCompleted:onDisposed:) + 65 (ObservableType+Extensions.swift:65)
    18 RxSwift                        0x1030a6b64 partial apply for closure #1 in ObservableType.subscribe(onNext:onError:onCompleted:onDisposed:) + 4373605220 (<compiler-generated>:4373605220)
    19 RxSwift                        0x10305947c AnonymousObserver.onCore(_:) + 22 (AnonymousObserver.swift:22)
    20 RxSwift                        0x1030a93c0 ObserverBase.on(_:) + 16 (ObserverBase.swift:16)
    21 RxSwift                        0x1030a957c protocol witness for ObserverType.on(_:) in conformance ObserverBase<A> + 4373615996 (<compiler-generated>:4373615996)
    22 RxSwift                        0x1030c9198 Sink.forwardOn(_:) + 35 (Sink.swift:35)
    23 RxSwift                        0x1030cc7fc SubscribeOnSink.on(_:) + 46 (SubscribeOn.swift:46)
    24 RxSwift                        0x1030cce50 protocol witness for ObserverType.on(_:) in conformance SubscribeOnSink<A, B> + 4373761616 (<compiler-generated>:4373761616)
    25 RxSwift                        0x1030c9198 Sink.forwardOn(_:) + 35 (Sink.swift:35)
    26 RxSwift                        0x1030d3ae0 TakeUntilSink._synchronized_on(_:) + 123 (TakeUntil.swift:123)
    27 RxSwift                        0x1030cfcb4 SynchronizedOnType.synchronizedOn(_:) + 15 (<compiler-generated>:15)
    28 RxSwift                        0x1030d39f0 TakeUntilSink.on(_:) + 118 (TakeUntil.swift:118)
    29 RxSwift                        0x1030d3e44 protocol witness for ObserverType.on(_:) in conformance TakeUntilSink<A, B> + 4373790276 (<compiler-generated>:4373790276)
    30 RxSwift                        0x1030c9198 Sink.forwardOn(_:) + 35 (Sink.swift:35)
    31 RxSwift                        0x1030826c8 AnonymousObservableSink.on(_:) + 50 (Create.swift:50)
    32 RxSwift                        0x1030828dc protocol witness for ObserverType.on(_:) in conformance AnonymousObservableSink<A> + 4373457116 (<compiler-generated>:4373457116)
    33 RxSwift                        0x1030a998c partial apply + 4373617036 (<compiler-generated>:4373617036)
    34 RxSwift                        0x103059578 AnyObserver.on(_:) + 36 (AnyObserver.swift:36)
    35 RxCocoa                        0x102f8ea88 partial apply for closure #1 in closure #1 in Reactive<A>.controlEvent(_:) + 331768 (<compiler-generated>:331768)
    36 RxCocoa                        0x102f50cf4 @objc ControlTarget.eventHandler(_:) + 78436 (<compiler-generated>:78436)
    37 libobjc.A.dylib                0x1a9418cc8 -[NSObject performSelector:withObject:withObject:] + 76
    38 UIKitCore                      0x1ad7f3df8 -[UIApplication sendAction:to:from:forEvent:] + 100
    39 UIKitCore                      0x1ad2027ec -[UIControl sendAction:to:forEvent:] + 208
    40 UIKitCore                      0x1ad202b60 -[UIControl _sendActionsForEvents:withEvent:] + 412
    41 UIKitCore                      0x1ad201b70 -[UIControl touchesEnded:withEvent:] + 524
    42 UIKitCore                      0x1ad82eb2c -[UIWindow _sendTouchesForEvent:] + 2084
    43 UIKitCore                      0x1ad82fe1c -[UIWindow sendEvent:] + 3336
    44 UIKitCore                      0x1ad80b9ac -[UIApplication sendEvent:] + 348
    45 UIKitCore                      0x1ad885df8 __dispatchPreprocessedEventFromEventQueue + 5712
    46 UIKitCore                      0x1ad8883b0 __handleEventQueueInternal + 4928
    47 UIKitCore                      0x1ad881534 __handleHIDEventFetcherDrain + 112
    48 CoreFoundation                 0x1a9680108 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
    49 CoreFoundation                 0x1a968005c __CFRunLoopDoSource0 + 84
    50 CoreFoundation                 0x1a967f7c8 __CFRunLoopDoSources0 + 184
    51 CoreFoundation                 0x1a967a694 __CFRunLoopRun + 1068
    52 CoreFoundation                 0x1a9679f40 CFRunLoopRunSpecific + 480
    53 GraphicsServices               0x1b38f7534 GSEventRunModal + 108
    54 UIKitCore                      0x1ad7f2a60 UIApplicationMain + 1940
    55 Less                           0x1027f82cc main + 16 (MeViewController.swift:16)
    56 libdyld.dylib                  0x1a94f8e18 start + 4
    
    opened by shahryar2001 9
  • Correct structure for PageCoordinator in TabBarCoordinator

    Correct structure for PageCoordinator in TabBarCoordinator

    Hi, First thanks for this great lib.

    Unfortunately I wasnt able to figure out, how should I structure this kind of relationship, I mean home many coordinators, and routers should I create.

    In My example I have Tab bar, in first tab there is view on top of screen, segmented control, under it should be container view and on segmented control change I should change View Controller in container view, I thought that it would be good to place page controller in container view, so I will change page controller's page.

    I have created HomeCoordinator: TabBarCoordinator TabRoute, in tabRoute I wrote all tabs cases, my understanding is first coordinator of tab bar, should be simple view controller, which will have segmented control and container view with pager, is this correct?

    Thanks, sorry for not so clear explanation

    question 
    opened by Narek1994 9
  • RedirectionalCoordinator.anyCoordinator crash  ‘Pushing a navigation controller is not supported’.

    RedirectionalCoordinator.anyCoordinator crash ‘Pushing a navigation controller is not supported’.

    that code works fine:

    let coordinator = AboutCoordinator(superCoordinator: self)
    return .push(coordinator
    

    but if you take anyCoordinator, from RedirectionalCoordinator,

    let coordinator = AboutCoordinator(superCoordinator: self)
    return .push(coordinator.anyCoordinator)
    

    it will be runtime crashed ‘Pushing a navigation controller is not supported’

    bug 
    opened by d2vydov 8
  • How implemented the PageCoordinator ?

    How implemented the PageCoordinator ?

    I have the root coordinator, HomeTabCoordinator, and this router I set two tabbar items, AboutController and NewsController and created for them coordinate classes.

    For NewsCoordinator I have next router:

    enum NewsListRouter: Route {
        case newsList
        case selectedNews(newsID: Int)
    }
    

    and I open the NewsDetailViewController next way:

    func detailNewsTrigger(_ id: Int) {
            router.trigger(.selectedNews(newsID: id))
        }
    

    For this screen I have the next design, buttons for news paging:

    Screenshot 2019-03-24 at 01 54 31

    How do I implement the function, when tapped for NextButton, showing next news and also, the previewButton, showing preview news ?

    I know about the PageCoordinator, but no idea how it implemented for my project, although looking at your example HomePageCoordinator. Please, help

    question 
    opened by tsomaev 8
  • Viewcontroller Can not release when use gesture to dismiss

    Viewcontroller Can not release when use gesture to dismiss

    I use .present(LoginCoordinator()) in prepareTransition. I'm going to call the trigger method to call .dismiss when I click the close button, that's fine, of course. But when I use the slide to make my 'presentViewController' disappear, this 'presentViewController' has a memory leak. I know because the coordinator didn't call 'removeChildrenIfNeeded', but, what should I do?

    opened by eyuxin 7
  • Carthage installation

    Carthage installation

    Hello, Can't install XCoordinator through Carthage

    ➜  Test carthage update --platform iOS
    *** Fetching XCoordinator
    *** Checking out XCoordinator at "2.0.7"
    *** xcodebuild output can be found in /var/folders/0z/h2fdfd0955qbnz45l8r0ykxh0000gn/T/carthage-xcodebuild.DGeErQ.log
    *** Downloading XCoordinator.framework binary at "XCoordinator 2.0.7"
    ***  Skipped installing XCoordinator.framework binary due to the error:
    	"Incompatible Swift version - framework was built with 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15) and the local version is 5.2.2 (swiftlang-1103.0.32.6 clang-1103.0.32.51)."
    
        Falling back to building from the source
    *** Building scheme "XCoordinator" in XCoordinator.xcodeproj
    Build Failed
    	Task failed with exit code 65:
    	/usr/bin/xcrun xcodebuild -project /Users/vadim/Dev/Sandbox/Test/Carthage/Checkouts/XCoordinator/XCoordinator.xcodeproj -scheme XCoordinator -configuration Release -derivedDataPath /Users/vadim/Library/Caches/org.carthage.CarthageKit/DerivedData/11.4.1_11E503a/XCoordinator/2.0.7 -sdk iphoneos ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES archive -archivePath /var/folders/0z/h2fdfd0955qbnz45l8r0ykxh0000gn/T/XCoordinator SKIP_INSTALL=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO CLANG_ENABLE_CODE_COVERAGE=NO STRIP_INSTALLED_PRODUCT=NO (launched in /Users/vadim/Dev/Sandbox/Test/Carthage/Checkouts/XCoordinator)
    
    This usually indicates that project itself failed to compile. Please check the xcodebuild log for more details: /var/folders/0z/h2fdfd0955qbnz45l8r0ykxh0000gn/T/carthage-xcodebuild.DGeErQ.log
    

    and the build log

        export variant=normal
        /bin/sh -c /Users/vadim/Library/Caches/org.carthage.CarthageKit/DerivedData/11.4.1_11E503a/XCoordinator/2.0.7/Build/Intermediates.noindex/ArchiveIntermediates/XCoordinator/IntermediateBuildFilesPath/XCoordinator.build/Release-iphoneos/XCoordinator.build/Script-9BC82EBE233D8CE400C604A8.sh
    /Users/vadim/Dev/Sandbox/Test/Carthage/Checkouts/XCoordinator: error: manifest parse error(s):
    <unknown>:0: warning: using sysroot for 'iPhoneOS' but targeting 'MacOSX'
    <unknown>:0: error: unable to load standard library for target 'x86_64-apple-macosx10.10'
    
    ** ARCHIVE FAILED **
    
    The following build commands failed:
    	PhaseScriptExecution Run\ Script /Users/vadim/Library/Caches/org.carthage.CarthageKit/DerivedData/11.4.1_11E503a/XCoordinator/2.0.7/Build/Intermediates.noindex/ArchiveIntermediates/XCoordinator/IntermediateBuildFilesPath/XCoordinator.build/Release-iphoneos/XCoordinator.build/Script-9BC82EBE233D8CE400C604A8.sh
    (1 failure)
    opened by alohabehappy 7
  • Is there any way to define Router object in single instance?

    Is there any way to define Router object in single instance?

    I always define a Router object when a view model's initializing. I want to see my view controllers at Xcode preview, that's why I need to define Router objects at preview state. Is it possible?

    opened by canbalkaya 1
  • Present fail on UIActivityViewController

    Present fail on UIActivityViewController

    tried to present an UIActivityViewController, but It always failed with log:

    2022-10-20 00:39:45.515868+0800 smartunify[958:102454] [default] LaunchServices: store (null) or url (null) was nil: Error Domain=NSOSStatusErrorDomain Code=-54 "process may not map database" UserInfo={NSDebugDescription=process may not map database, _LSLine=66, _LSFunction=_LSServer_GetServerStoreForConnectionWithCompletionHandler} 2022-10-20 00:39:45.516026+0800 smartunify[958:102454] [default] Attempt to map database failed: permission was denied. This attempt will not be retried. 2022-10-20 00:39:45.516119+0800 smartunify[958:102454] [db] Failed to initialize client context with error Error Domain=NSOSStatusErrorDomain Code=-54 "process may not map database" UserInfo={NSDebugDescription=process may not map database, _LSLine=66, _LSFunction=_LSServer_GetServerStoreForConnectionWithCompletionHandler} 2022-10-20 00:39:45.568113+0800 smartunify[958:102454] [default] LaunchServices: store (null) or url (null) was nil: Error Domain=NSOSStatusErrorDomain Code=-54 "process may not map database" UserInfo={NSDebugDescription=process may not map database, _LSLine=66, _LSFunction=_LSServer_GetServerStoreForConnectionWithCompletionHandler} 2022-10-20 00:39:45.568205+0800 smartunify[958:102454] [default] Attempt to map database failed: permission was denied. This attempt will not be retried. 2022-10-20 00:39:45.568266+0800 smartunify[958:102454] [db] Failed to initialize client context with error Error Domain=NSOSStatusErrorDomain Code=-54 "process may not map database" UserInfo={NSDebugDescription=process may not map database, _LSLine=66, _LSFunction=_LSServer_GetServerStoreForConnectionWithCompletionHandler}

    opened by Ledvance-Beet 0
  • Is there a way to dismiss a specific Presentable?

    Is there a way to dismiss a specific Presentable?

    I'm wondering if there is a way to dismiss a specific Presentable. Let's say I have NavigationController on which I have presented three different presentables (modals) in the following order: A -> B -> C

    Later on I would like to dismiss B modal while A and C shouldn't be changed at all. (new stack: A -> B)

    I was looking for something like this in the prepareTransition method

    case .routeDismissB:
       let presentableB: UIViewController = // found in the current stack
       return .dismiss(presentableB)
    

    Obviously, I can do something like that, but it would break the deep linking

    case .routeDismissB:
       let presentableB: UIViewController = // found in the current stack
       presentableB.dismiss(animated: true)
       
       return .none()
    

    Thank you in advance for any tips :)

    opened by SzymonWojcikCrustlab 0
  • Why nothing happens in you demo?

    Why nothing happens in you demo?

    Hey, I have a question. In XCoordinator-Example The demo just presents UserViewController, when I click the cell In the UserListViewController. Screen Shot 2022-03-14 at 10 57 46 PM however, In UserCoordinator: Screen Shot 2022-03-14 at 10 52 40 PM it is executed but nothing happens.

    opened by Changzw 0
  • macOS support

    macOS support

    Hey folks, XCoordinator looks great. Is there any possibility of adding support for macOS? Considering that you're planning to add support for watchOS, it'd be nice to cover the desktop as well!

    opened by saket 8
Releases(2.2.0)
Owner
QuickBird Studios
Overly friendly team of developers driven to push the state of Mobile Development forward (we're hiring!)
QuickBird Studios
Implementation of the repository pattern in Swift, using generics.

Store Simple, powerful and elegant implementation of the repository pattern, using generics. Why? ?? There are a couple of ways to implement the Repos

Narek Mailian 2 Aug 31, 2022
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
A configurable api client based on Alamofire4 and RxSwift4 for iOS

SimpleApiClient A configurable api client based on Alamofire4 and RxSwift4 for iOS Requirements iOS 8.0+ Swift 4 Table of Contents Basic Usage Unwrap

Jay 67 Dec 7, 2020
Flexible, stream-based abstraction for launching processes

ReactiveTask ReactiveTask is a Swift framework for launching shell tasks (processes), built using ReactiveSwift. let strings = [ "foo\n", "bar\n", "bu

Carthage 131 Nov 5, 2022
iOS & OSX Bluetooth library for RxSwift

RxBluetoothKit is a Bluetooth library that makes interaction with BLE devices much more pleasant. It's backed by RxSwift and CoreBluetooth and it prov

Polidea 1.3k Dec 16, 2022
A super simple library for state management with unidirectional data flow.

OneWay ?? OneWay is still experimental. As such, expect things to break and change in the coming months. OneWay is a super simple library for state ma

SeungYeop Yeom 41 Dec 20, 2022
RxSwift bindings for Permissions API in iOS.

RxPermission RxSwift bindings for Permission API that helps you with Permissions in iOS. Installation RxPermission is available through CocoaPods. I c

Luke 230 Dec 27, 2022
Reactive Keyboard in iOS

RxKeyboard RxKeyboard provides a reactive way of observing keyboard frame changes. Forget about keyboard notifications. It also perfectly works with U

RxSwift Community 1.4k Dec 29, 2022
Two-way data binding framework for iOS. Only one API to learn.

BindKit A simple to use two-way data binding framework for iOS. Only one API to learn. Supports Objective-C, Swift 5, Xcode 10.2, iOS 8 and above. Shi

Electric Bolt 13 May 25, 2022
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
Sample iOS application in SwiftUI presenting Redux architecture

SwiftUI-Redux-Demo Sample iOS application in SwiftUI presenting Redux architecture. My full article about Redux in detail you will find here: Redux ar

Wojciech Kulik 25 Nov 27, 2022
iOS app for open event

CircleCI Code Quality Chat Open Event iOS iOS app for Open Event Introduction This is an iOS app developed for FOSSASIA in mind. The Open Event Projec

FOSSASIA 1.6k Jan 5, 2023
Open Event Orga iOS App

Open Event Organizer iOS App Event management app for organizers using Open Event Platform Roadmap Make the app functionality and UI/UX similar to the

FOSSASIA 1.5k Dec 10, 2022
MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner

CZInstagram MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner. Unidirectional Da

Cheng Zhang 56 Nov 1, 2022
Store-App - Store app made for IOS using Swift programming language

Store-App Store app views products, cart, and using login from https://fakestore

Anas Khalil 2 Jan 1, 2022
A clone for netflix iOS app for learning.

Netflix Clone ???? Um clone do aplicativo da Netflix voltado para estudos, em Swift, voltado para iOS. O aplicativo conta com uma home, tela de novida

Amanda Detofol Constante 2 May 28, 2022
Powerful navigation in the Composable Architecture via the coordinator pattern

TCACoordinators The coordinator pattern in the Composable Architecture TCACoordinators brings a flexible approach to navigation in SwiftUI using the C

John Patrick Morgan 231 Jan 7, 2023
Eugene Kazaev 713 Dec 25, 2022
RxFlow is a navigation framework for iOS applications based on a Reactive Flow Coordinator pattern

About Navigation concerns RxFlow aims to Installation The key principles How to use RxFlow Tools and dependencies GitHub Actions Frameworks Platform L

RxSwift Community 1.5k May 26, 2021
A navigation frameword based on the Coordinator pattern and is a compact version from XCoordinator.

Coordinator A navigation frameword based on the Coordinator pattern and is a compact version from XCoordinator. Example To run the example project, cl

Duc Pham 3 Jul 9, 2022