Skip to content

A basic coordinator pattern implementation with presenter approach. Coordinators are managing the screen flow of your app and coordinating the connection between the UI and your models.

License

Notifications You must be signed in to change notification settings

JamitLabs/JLCoordinator

Repository files navigation

JLCoordinator Framework

A basic coordinator pattern implementation with presenter approach. Coordinators are managing the screen flow of your app and coordinating the connection between the UI and your models.

Installation

The framework is configured to work with Swift Package Manager. Just add it to your dependencies.

Components

Coordinator Class

The framework is clustered into to main components the Coordinator and the Presenter. The Coordinator is used to handle the screen flow and the event delegation of its corresponding UIViewController. For example if a button is tapped on a view controller the coordinator should be informed about this. The coordinator then is able to handle the event and can for example start another screen flow. For more details just take a look into our example project which is included.

Presenter Protocol

The Presenter is handling the way how the UIViewController of a Coordinator is presented. In some cases we need the flexibility to present UIViewController in different ways, for example modally or in a navigation stack. This is now solved through the Presenter just start the Coordinator with the presenter implementation of your choice. All coordinators which use a presenter are getting informed about a dismissal or presentation of an UINavigationController through the predefined functions in the Coordinator.

Another advantage of the Presenter is the handling of common UIKit events such as dismiss through swipe back on an UINavigationController or the in iOS 13 introduced adaptive dismiss for modally presented screens. If such an event occurs the coordinators which are using the presenter are informed about the dismissal of the UIViewController no matter how it was dismissed.

Predefined presenter implementations for the most use cases are ready to use (see list below). If you have another use case, for example embedding an UIViewController, you can implement the Presenter protocol.

Predefined implementations

InitialNavigationPresenter
Is used to present a NavigationController stack in a `UIWindow` (e.g. used in `AppDelegate` or `SceneDelegate`). The first `UIViewController` will be set as `rootViewController` all further `UIViewControllers` will be pushed onto the stack.
InitialPresenter
Is used to present in a `UIWindow` (e.g. used in `AppDelegate` or `SceneDelegate`)
ModalPresenter
Presents the coordinators `UIViewController` modally on a *PresentingViewController*. There is a `ModalPresentationConfiguration` where you can set the `modalTransitionStyle` as well as the `modalPresentationStyle`.
ModalNavigationPresenter
Presents a new NavigationController stack modally on a *PresentingViewController*. The first `UIViewController` will be set as `rootViewController` all further `UIViewControllers` will be pushed onto the stack. There is a `ModalPresentationConfiguration` where you can set the `modalTransitionStyle` as well as the `modalPresentationStyle`.
NavigationPresenter
Is initialized with an `UINavigationController` the first presented `UIViewController` is set as `rootViewController` the following are pushed onto the stack.
TabPresenter
Is used for presenting `UIViewController` embedded in a `UITabBarController` which has to be passed to the `TabPresenter`.
TabNavigationPresenter
Is used for presenting `UINavigationController` embedded in a `UITabBarController` which has to be passed to the `TabNavigationPresenter `. The first `UIViewController` is set as `rootViewController` all further will be pushed onto the stack.

Usage

Basic

To start implementing an app using the coordinator framework you should start by subclassing Coordinator and overriding its start() function. In most cases you have to present the rootViewController of the Coordinator in this function.

import JLCoordinator
import UIKit

final class MyCoordinator: Coordinator {
    private let someViewController: UIViewController = UIStoryboard(name: "ViewController", bundle: nil).instantiateViewController(identifier: "ViewController")
    
    override func start() {
        super.start()
        someViewController.delegate = self
        presenter.present(someViewController, animated: true)
    }
}

After that go to your AppDelegate/SceneDelegate and initialize your coordinator with the given InitialPresenter.

import JLCoordinator
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var myCoordinator: MyCoordinator?

    func scene(
        _ scene: UIScene,
        willConnectTo session: UISceneSession,
        options connectionOptions: UIScene.ConnectionOptions
    ) {
        guard let windowScene = (scene as? UIWindowScene) else { return }

        let window: UIWindow = .init(windowScene: windowScene)
        myCoordinator = .init(presenter: InitialPresenter(window: window))
        myCoordinator?.start()
    }
}

Starting a child coordinator

Starting a child coordinator is really easy, just instantiate your child with a presenter of your choice, add the coordinator to the parent and call the start() function of the child.

func startNextCoordinator() {
    let child: MyChildCoordinator = .init(presenter: ModalPresenter(presentingViewController: viewController))
    add(childCoordinator: child)
    child.start()
}

Stopping a coordinator

If you want to stop a coordinator you just have to call the stop() function. If you call stop() not only the one coordinator is stopped but it will stop all children and afterwards it informs the parent coordinator that it has been removed (didRemove(child:)) and stopped (didStop(child:)).

Getting informed about completed dismissals and presententations

The Coordinator provides through implementing the PresenterObserving protocol several functions to get informed about the dismiss and present completion of UIViewControllers or UINavigationController. If you want to get informed about the dismissal of a UIViewController just override following functions:

// If an UIViewController is dismissed this function is called
override func presenter(_ presenter: Presenter, didDismiss viewController: UIViewController) {
    // Do some stuff what should happend if the viewController is dismissed
    // for example: stop()
}

// If an UINavigationController is dismissed this function is called
override func presenter(_ presenter: Presenter, didDismiss navigationController: UINavigationController) {
    // Do some stuff what should happend if the navigationController is dismissed
    // You could check if this was the NavigationController of a specific UIViewController
    guard myViewController.navigationController === navigationController else { return }

    stop()
}

If you want to get informed about the completed present process just override following function:

override func presenter(_ presenter: Presenter, didPresent viewController: UIViewController) {
    // Do some stuff
}

For further examples please take a look into the included example project.

Development

These tools are required for development of the project. Please make sure to have installed the correct versions or install them for example via Homebrew 🍻

Tool Version
Xcode 11.5
Swift 5.2
SwiftLint min 0.38.2

SwiftLint

SwiftLint is used to enforce our code styles and conventions. The configuration file is placed in the root folder of the project.

About

A basic coordinator pattern implementation with presenter approach. Coordinators are managing the screen flow of your app and coordinating the connection between the UI and your models.

Resources

License

Stars

Watchers

Forks

Packages

No packages published