A simple, powerful and elegant implementation of the coordinator template in Swift for UIKit

Overview


A simple, powerful and elegant implementation of the coordinator template in Swift for UIKit

Installation

Swift Package Manager

https://github.com/bartleby/Jumper.git

How to use Jumper

Coordinators

Jumper out of the box has three kinds of coordinator protocols that your coordinators can implement:

You can also implement your own coordinator, see the source code.

  • RootCoordinable - It is a UIViewController container, great for starting logic in your application.
  • NavigationCoordinable - Used for stack navigation
  • TabCoordinable - Implements 'UITabBarController`

RootCoordinable

final class AppCoordinator: RootCoordinable {

    // step 1
    var navigation: RootNavigation<AppCoordinator> = .init(initial: \.onboarding)
    
    // step 2
    @Route var onboarding = onboardingScreen
    @Route var home = homeScreen

    // step 3
    func onboardingScreen() -> ScreenView {
        OnboardingScreenView()
    }
    
    func homeScreen() -> TabBarCoordinator {
        TabBarCoordinator()
    }
}

In this example, an AppCoordinator is created that implements the RootCoordinable protocol

  • Step 1 - You have to implement the navigation property and initialize it with Route by default
  • Step 2 - Initialization of transitions, set using the keyword @Route which should indicate the function of creating a View or another Coordinator for the transition
  • Step 3 - Implementation of methods referenced by Route

List of transition methods

  • root(\.someRoute) Replaces the current view or coordinator
  • root(\.someRoute, input: "any type")
  • isRoot(\.someRoute) Returns a boolean value that indicates whether the given Route is root
  • hasRoot(\.someRoute) Returns the root coordinator or nil if the specified Route is not root
  • present(\.someRoute) Is presented by a view or coordinator
  • present(\.someRoute, input: "any type")
  • present(\.someRoute, input: "any type", animated: false)
  • dismiss() Dismiss the current Coordinator

You can pass an argument to each transition method using the input field, which will be passed to the view/coordinator creation function.

@Route var userList = userListScreen

func userListScreen(listData: [User]) -> UserListCoordinator {
    UserListCoordinator(data: listData)
}

calling such a transition will look like this

coordinator.present(\.userList, input: userListData)

NavigationCoordinable

final class AuthorizationCoordinator: NavigationCoordinable {
    
    var navigation: Navigation<AuthorizationCoordinator> = .init(initial: \.authorization)
    
    @Route var authorization = authorizationScreen
    @Route var registration = registrationScreen
    
    func authorizationScreen() -> ScreenView {
        AuthorizationScreen()
    }
    
    func registrationScreen() -> ScreenView {
        RegistrationScreen()
    }
}

List of transition methods

  • push(\.someRoute)
  • push(\.someRoute, input: "any type")
  • push(\.someRoute, animated: false)
  • push(stack: )
  • pop(\.someRoute)
  • pop(\.someRoute, animated: false)
  • pop(to: \.someRoute) going to the specified Route
  • pop(to: \.someRoute, animated: false)
  • popToRoot()
  • popToRoot(animated: false)
  • present(\.someRoute)
  • present(\.someRoute, input: "any type")
  • present(\.someRoute, input: "any type", animated: false)
  • dismiss() Dismiss the current Coordinator

using the push(stack:) method you can push several Routes into the navigation stack at once, and the animation will be applied only for the last transition

coordinator.push {
    \SettingsCoordinator.yellow
    \SettingsCoordinator.green
    \SettingsCoordinator.green
    \SettingsCoordinator.green
    \SettingsCoordinator.yellow
    \SettingsCoordinator.green
}

you can do the same with the Route chain

coordinator
    .push(\.yellow, animated: false)
    .push(\.green, animated: false)
    .push(\.green, animated: false)
    .push(\.green, animated: false)
    .push(\.yellow, animated: false)
    .push(\.green, animated: true)

TabCoordinable

final class TabBarCoordinator: TabCoordinable {
    
    // Step 1
    var navigation: TabNavigation<TabBarCoordinator> = .init {
        \TabBarCoordinator.main
        \TabBarCoordinator.settings
    }
    
    // Step 2
    @Route(tabItem: mainTab) var main = mainScreen
    @Route(tabItem: settingsTab) var settings = settingsScreen

    // Step 3
    func mainScreen() -> MainCoordinator {
        MainCoordinator()
    }
    
    func settingsScreen() -> SettingsCoordinator {
        SettingsCoordinator()
    }
    
    // Step 4
    func mainTab() -> UITabBarItem {
        UITabBarItem()
            .image(UIImage(systemName: "circle.fill"))
            .title("Main")
    }
    
    func settingsTab() -> UITabBarItem {
        UITabBarItem()
            .image(UIImage(systemName: "square.fill"))
            .title("Settings")
    }
}

Here everything is similar to other coordinators, with a small exception, a new argument appears in @Route, to which you must pass TabItem, and in the initialization of the navigation property, a list of routes that will be tabs is now passed

  • Step 1 - Initialize navigation with several Routes that will be tabs in the tabbar
  • Step 2 - Define Route by specifying TabItem in the argument
  • Step 3 - Define the coordinators for transitions
  • Step 4 - Define the methods that will return TabItem's

List of transition methods

  • focus(\.someTabRoute) switching to tab
  • present(\.someRoute) presentation of Route
  • dismiss() Dismiss the current Coordinator

Alert's

In order to show the alert and other pop-up elements through the coordinator, you must support the ScreenViewPresentable protocol

For example, create the AlertCoordinable protocol and implement the Alert display logic in it. To get the controller to which you want to show the popup element, call the view() method

let controller = view()

public protocol AlertCoordinable: ScreenViewPresentable {
    func showAlert(title: String, message: String)
}

extension AlertCoordinable {
    func presentAlert(title: String, message: String) {
        let controller = view()
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let completeAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
        alertController.addAction(completeAction)
        controller.present(alertController, animated: true, completion: nil)
    }
}

Implement the 'AlertCoordinable` protocol in the coordinator. Now the coordinator has the opportunity to display an alert

final class SettingsCoordinator: NavigationCoordinable, AlertCoordinable {
    //...
}
coordinator.presentAlert(title: "Title", message: "Message")

Customizing

Sometimes you may need to access the UITabBarController, UINavigationController or UIViewController controller from your coordinator, there is a configure(controller: ) method for this

final class MainCoordinator: NavigationCoordinable {
    
    //...
    
    func configure(controller: UINavigationController) {
        // Customize here
    }
}

Chaining

One of the strengths of Jumper is the integration of transitions into chains

coordinator
    .hasRoot(\.tabBar)
    .focus(\.todoList)
    .push(\.todoDetail, input: todoIdentifier)
    .present(\.todoEditor)

each transition, if it is a transition to the coordinator, returns the transition coordinator, if it is a transition to the view, then the current coordinator is returned.

For example: There are two transitions in the SettingsCoordinator coordinator:

final class SettingsCoordinator: NavigationCoordinable {
    
    //...
    
    @Route var rateApp = rateAppScreen
    @Route var notification = notificationScreen

    func rateAppScreen() -> ScreenView {
        RateAppViewController()
    }
    
    func notificationScreen() -> NotificationCoordinator {
        NotificationCoordinator()
    }
}
coordinator.present(\.rateApp) \\ return SettingsCoordinator
coordinator.present(\.notification) \\ return NotificationCoordinator

Deep Linking

By combining Route into chains, you get DeepLink out of the box, to implement them, define the scene(scene:, openURLContexts:) method in SceneDelegate

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    appCoordinator.onOpenURL(URLContexts.first?.url)
}

define the onOpenURL(url:) method in your app coordinator

func onOpenURL(_ url: URL?) {
    guard let url = url else { return }
    guard let deepLink = try? DeepLink(url: url) else { return }

    if let coordinator = self.hasRoot(\.home) {
        switch deepLink {
        case .todo(let id):
            coordinator
                .focus(\.main)
                .present(\.todo, input: id)
        case .settings:
            coordinator.focus(\.settings)
        case .home:
            coordinator.focus(\.main)
        }
    }
}

You can see the implementation of DeepLink in the Demo project.

To test the 'DeepLink ' use the terminal command xcrun simctl openurl booted <url>

deep links that are configured in the Demo project

Switching to Main Tab

xcrun simctl openurl booted jumper://io.idevs/home

Switching to Settings Tab

xcrun simctl openurl booted jumper://io.idevs/settings

Opens the modal view and passes the hello-world argument to it

xcrun simctl openurl booted jumper://io.idevs/todo/hello-world

ezgif-4-59b7108f97

Demo project

Download the demo project from repository

License

MIT license. See the LICENSE file for details.

You might also like...
Appz 📱 Launch external apps, and deeplink, with ease using Swift!
Appz 📱 Launch external apps, and deeplink, with ease using Swift!

Appz 📱 Deeplinking to external applications made easy Highlights Web Fallback Support: In case the app can't open the external application, it will f

🎯Linker  Lightweight way to handle internal and external deeplinks in Swift for iOS
🎯Linker Lightweight way to handle internal and external deeplinks in Swift for iOS

Linker Lightweight way to handle internal and external deeplinks in Swift for iOS. Installation Dependency Managers CocoaPods CocoaPods is a dependenc

Monarch Router is a Declarative URL- and state-based router written in Swift.
Monarch Router is a Declarative URL- and state-based router written in Swift.

Monarch Router is a declarative routing handler that is capable of managing complex View Controllers hierarchy transitions automatically, decoupling View Controllers from each other via Coordinator and Presenters. It fits right in with Redux style state flow and reactive frameworks.

🍞 [Beta] A view controller that can unwind like presentation and navigation.

FluidPresentation - no more handling presented or pushed in view controller A view controller that supports the interactive dismissal by edge pan gest

Easy and maintainable app navigation with path based routing for SwiftUI.

Easy and maintainable app navigation with path based routing for SwiftUI.

iOS routing done right. Handles both URL recognition and controller displaying with parsed parameters. All in one line, controller stack preserved automatically!
iOS routing done right. Handles both URL recognition and controller displaying with parsed parameters. All in one line, controller stack preserved automatically!

Developed and Maintained by Ipodishima Founder & CTO at Wasappli Inc. (If you need to develop an app, get in touch with our team!) So what is this lib

An open source library for building deep-linkable SwiftUI applications with composition, testing and ergonomics in mind
An open source library for building deep-linkable SwiftUI applications with composition, testing and ergonomics in mind

Composable Navigator An open source library for building deep-linkable SwiftUI applications with composition, testing and ergonomics in mind Vanilla S

A framework for easily testing Push Notifications and Routing in XCUITests
A framework for easily testing Push Notifications and Routing in XCUITests

Mussel 🦪 💪 A framework for easily testing Push Notifications, Universal Links and Routing in XCUITests. As of Xcode 11.4, users are able to test Pus

A bidirectional Vapor router with more type safety and less fuss.

vapor-routing A routing library for Vapor with a focus on type safety, composition, and URL generation. Motivation Getting started Documentation Licen

Owner
Aleksei Artemev
iOS, Mac Developer & Designer. I code and design great simple things. It is a passion of my life.
Aleksei Artemev
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 compact Coordinator from XCoordinator

A lightweight navigation framework based on the Coordinator pattern and is a compact version from XCoordinator.

Duc Pham 3 Jul 9, 2022
Marshroute is an iOS Library for making your Routers simple but extremely powerful

Marshroute Contents Overview Detailes Tuning the transition animation 3d touch support PeekAndPopUtility Peek and pop state observing Demo Requirement

avito.tech 215 Jan 4, 2023
⛵️ URLNavigator provides an elegant way to navigate through view controllers by URLs.

URLNavigator ⛵️ URLNavigator provides an elegant way to navigate through view controllers by URLs. URL patterns can be mapped by using URLNavigator.re

Suyeol Jeon 2.6k May 27, 2021
DZURLRoute is an Objective-C implementation that supports standard-based URLs for local page jumps.

DZURLRoute Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements s.dependency 'DZVie

yishuiliunian 72 Aug 23, 2022
An extremely lean implementation on the classic iOS router pattern.

Beeline is a very small library that aims to provide a lean, automatic implementation of the classic iOS router pattern.

Tim Oliver 9 Jul 25, 2022
URL routing library for iOS with a simple block-based API

JLRoutes What is it? JLRoutes is a URL routing library with a simple block-based API. It is designed to make it very easy to handle complex URL scheme

Joel Levin 5.6k Jan 6, 2023
🛣 Simple Navigation for iOS

Router Reason - Get Started - Installation Why Because classic App Navigation introduces tight coupling between ViewControllers. Complex Apps navigati

Fresh 457 Jan 4, 2023
An App-specific Simple Routing Library

TrieRouter An App-specific Simple Routing Library Usage let r = Router() r.addRoute("tbx://index") { _ in print("root") } r.addRoute("tbx://intTes

TBXark 2 Mar 3, 2022
Interface-oriented router for discovering modules, and injecting dependencies with protocol in Objective-C and Swift.

ZIKRouter An interface-oriented router for managing modules and injecting dependencies with protocol. The view router can perform all navigation types

Zuik 631 Dec 26, 2022