ALCoodinator

This repository contains a library implementing the Coordinator pattern, which is a design pattern used in iOS app development to manage app navigation flows. The library provides a set of classes and protocols that can be used to implement the Coordinator pattern in an iOS app. It works either UIKit or SwiftUI apps

Its core navigation has created with UINavigationController (UIKit) with the aim to get profit about navigation stack.

Getting Started

To use the Coordinator pattern library in your iOS project, you’ll need to add the library files to your project and set up a Coordinator object. Here are the basic steps:

  1. Create a SceneDelegate class if your app supports scenes:

import UIKit
import ALCoordinator

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var mainCoordinator: Coordinator

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: windowScene)
        window?.makeKeyAndVisible()
        setupCoordinator(window: window, animated: true)
    }
    
    private func setupCoordinator(window: UIWindow?, animated: Bool = false) {
      mainCoordinator = .init()
      window?.rootViewController = mainCoordinator.root
      mainCoordinator?.start(animated: animated)
      BaseCoordinator.mainCoordinator = mainCoordinator
    }

}
  1. In your app’s AppDelegate file, set the SceneDelegate class as the windowScene delegate:

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}

    // Add this method
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let sceneDelegate = SceneDelegate()
        sceneDelegate.scene(windowScene, willConnectTo: session, options: connectionOptions)
    }
}
  1. Create class MainCoordinator:

class MainCoordinator: BaseCoordinator {
  
  init() {
    super.init(parent: nil)
  }
  
  override func start(animated: Bool = false) {
    let coordinator = OnboardingCoordinator(withParent: self)
    coordinator.start(animated: animated)
  }
}
  1. Create custom coordinator:
  • SwiftUI:

    class OnboardingCoordinator: CoordinatorSUI<OnboardingRouter> {
    
      override func start(animated: Bool) {
        show(.firstStep)
        parent.startChildCoordinator(self, animated: animated)
      }
    
      func showStep2() {
        show(.secondStep)
      }
    
      func showLoginCoordinator() {
        let coordinator = LoginCoordinator()
        coordinator.start()
      }
    }
    
    enum OnboardingRouter: NavigationRouter {
    
      case firstStep
      case secondStep
    
      // MARK: NavigationRouter
      var transition: NavigationTranisitionStyle {
        switch self {
          case .firstStep, secondStep:
            return .push
        }
      }
    
      func view() -> any View {
        switch self {
          case .firstStep:
            return FirstStepView()
          case .secondStep:
            return SecondStepView()
        }
      }
    }
  • UIKit:

    class OnboardingCoordinator: BaseCoordinator {
    
      override func start(animated: Bool) {
        let vc = FirstViewController()
        root.viewControllers.append(vc)
        parent.startChildCoordinator(self, animated: animated)
      }
    
      func showStep2() {
        let vc = SecondViewController()
        push(vc)
      }
    
      func showLoginCoordinator() {
        let coordinator = LoginCoordinator()
        coordinator.start()
      }
    }

How build Tabbar?

  1. Create a router

    enum HomeRouter: CaseIterable, TabbarPage {
    
      case marketplace
      case settings
    
      // MARK: NavigationRouter
    
      func coordinator(parent: Coordinator) -> Coordinator {
        switch self {
          case .settings:
            return SettingCoordinator(parent: parent)
          case .marketplace:
            return MarketplaceCoordinator(parent: parent)
        }
      }
    
      // MARK: TabbarPageDataSource
    
      public var title: String {
        switch self {
          case .marketplace:
            return "Marketplace"
          case .settings:
            return "Settings"
        }
      }
    
      public var icon: String {
        switch self {
          case .marketplace:
            return "house"
          case .settings:
            return "gearshape"
        }
      }
    
      public var position: Int {
        switch self {
          case .marketplace:
            return 0
          case .settings:
            return 1
        }
      }
    }
  2. Create a TabbarCoordinator

  • Default tabbar build with UIKIT (It also works with SwiftUI)

    class HomeCoordinator: TabbarCoordinatorSUI<HomeRouter> {
      public init(withParent parent: Coordinator) {
        let pages: [Router] = [.marketplace, .settings]
        super.init(withParent: parent, pages: pages)
      }
    }
  • Custom view (SwiftUI)

    class HomeCoordinator: TabbarCoordinatorSUI<HomeRouter> {
      public init(withParent parent: Coordinator) {
        let pages: [Router] = [.marketplace, .settings]
        let view = HomeTabbarView(pages: pages)
        super.init(withParent: parent, pages: pages, customView: .custom(value: view))
        view.$currentPage
          .sink { [weak self] page in
            self?.tabController.selectedIndex = page.position
          }.store(in: &cancelables)
      }
    }

Actions:

Actions you can perform from the coordinator depends on the kind of coordinator used. For instance, using a BaseCoordinator, CoordinatorSUI or Coordinator some of the functions you can perform are:

Name Description
root variable to get navigation controller.
start() Starts the navigation flow managed by the coordinator. This method should be called to begin a navigation flow. Params: animated: Bool default true
finish() Finishes the navigation flow managed by the coordinator. This method should be called to end a navigation flow. Params: completion: (() -> Void)?, default nil animated: Bool, default true
push(_:) Pushes a view controller onto the receiver’s stack and updates the display (only for UIKit). Params: viewController: UIViewController animated: Bool, default true
present(_:) Presents a view controller modally (only for UIKit). Params: viewController: UIViewController animated: Bool, default true completion: (() -> Void)?, default nil
pop(_:) Pops the top view controller from the navigation stack and updates the display . Params: animated: Bool, default true
popToRoot(_:) Pops all the view controllers on the stack except the root view controller and updates the display. Params: animated: Bool, default true
popToView(_:) Pops view controllers until the specified view controller is at the top of the navigation stack. if the view is onto navigation stack returns true. e.i: popToView(MyObject.self, animated: false). Params: view: Any animated: Bool, default true
dismiss(_:) Dismisses the view controller that was presented modally by the view controller. Params: completion: (() -> Void)?, default nil animated: Bool, default true
close(_:) If a view controller is presented as modal, it calls the dismiss(:)> function; otherwise, pop(:). Params: completion: (() -> Void)?, default nil animated: Bool, default true
topCoordinator(_:) Returns the last coordinator presented
restart(_:) Finish all its children and finally call start() function. Paramss completion: (() -> Void)?, default nil animated: Bool, default true
startChildCoordinator(_:) It is a faster way to initialize a secondary coordinator. Inserting a child to its child coordinators and finally it calls present(:) function. Paramss coordinator: Coordinator, child coordinator animated: Bool, default true

Classes

In addition to the functions listed above, the Coordinator-pattern library provides several classes that can be used to simplify the implementation of the Coordinator pattern in an iOS app. These classes are:

BaseCoordinator The BaseCoordinator class provides a basic implementation of the Coordinator protocol, with default implementations. This class can be subclassed to create custom coordinator objects that implement the Coordinator protocol.

TabbarCoordinatorSUI The TabbarCoordinatorSUI class is a specialized coordinator object that is designed to manage the navigation flow of a tab bar interface. This class provides methods for adding child coordinators for each tab in the tab bar, and for managing the selection of tabs.

CoordinatorSUI The CoordinatorSUI class is a specialized coordinator object that is designed to manage the navigation flow of a SwiftUI app. This class provides methods for showing views.

TabbarPage The typealias TabbarPage is a short way to implement protocols TabbarPageDataSource & TabbarNavigationRouter

Installation ?

SPM

Open Xcode and your project, click File / Swift Packages / Add package dependency… . In the textfield “Enter package repository URL”, write https://github.com/felilo/AFCoordinator/ and press Next twice

Contributing

Contributions to the ALCoordinator library are welcome! To contribute, simply fork this repository and make your changes in a new branch. When your changes are ready, submit a pull request to this repository for review.

License

The ALCoordinator library is released under the MIT license. See the LICENSE file for more information.

GitHub

View Github