Coordinators Essential tutorial

Overview

ApplicationCoordinator

A lot of developers need to change navigation flow frequently, because it depends on business tasks. And they spend a huge amount of time for re-writing code. In this approach, I demonstrate our implementation of Coordinators, the creation of a protocol-oriented, testable architecture written on pure Swift without the downcast and, also to avoid the violation of the S.O.L.I.D. principles.

Based on the post about Application Coordinators khanlou.com and Application Controller pattern description martinfowler.com.

Coordinators Essential tutorial. Part I medium.com

Coordinators Essential tutorial. Part II medium.com

Example provides very basic structure with 6 controllers and 5 coordinators with mock data and logic.

I used a protocol for coordinators in this example:

protocol Coordinator: class {
    func start()
    func start(with option: DeepLinkOption?)
}

All flow controllers have a protocols (we need to configure blocks and handle callbacks in coordinators):

protocol ItemsListView: BaseView {
    var authNeed: (() -> ())? { get set }
    var onItemSelect: (ItemList -> ())? { get set }
    var onCreateButtonTap: (() -> ())? { get set }
}

In this example I use factories for creating coordinators and controllers (we can mock them in tests).

protocol CoordinatorFactory {
    func makeItemCoordinator(navController navController: UINavigationController?) -> Coordinator
    func makeItemCoordinator() -> Coordinator
    
    func makeItemCreationCoordinatorBox(navController: UINavigationController?) ->
        (configurator: Coordinator & ItemCreateCoordinatorOutput,
        toPresent: Presentable?)
}

The base coordinator stores dependencies of child coordinators

class BaseCoordinator: Coordinator {
    
    var childCoordinators: [Coordinator] = []

    func start() { }
    func start(with option: DeepLinkOption?) { }
    
    // add only unique object
    func addDependency(_ coordinator: Coordinator) {
        
        for element in childCoordinators {
            if element === coordinator { return }
        }
        childCoordinators.append(coordinator)
    }
    
    func removeDependency(_ coordinator: Coordinator?) {
        guard
            childCoordinators.isEmpty == false,
            let coordinator = coordinator
            else { return }
        
        for (index, element) in childCoordinators.enumerated() {
            if element === coordinator {
                childCoordinators.remove(at: index)
                break
            }
        }
    }
}

AppDelegate store lazy reference for the Application Coordinator

var rootController: UINavigationController {
    return self.window!.rootViewController as! UINavigationController
  }
  
  private lazy var applicationCoordinator: Coordinator = self.makeCoordinator()
  
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let notification = launchOptions?[.remoteNotification] as? [String: AnyObject]
    let deepLink = DeepLinkOption.build(with: notification)
    applicationCoordinator.start(with: deepLink)
    return true
  }
  
  private func makeCoordinator() -> Coordinator {
      return ApplicationCoordinator(
        router: RouterImp(rootController: self.rootController),
        coordinatorFactory: CoordinatorFactoryImp()
      )
  }
Comments
  • Coordinatable -> Coordinator

    Coordinatable -> Coordinator

    In Readme is written Coordinatable, the example Coordinator:

    protocol Coordinatable: class { func start() func start(with option: DeepLinkOption?) }

    correct it if mistake.

    opened by anatoliykant 2
  • Пара вопросов по работе паттерна

    Пара вопросов по работе паттерна

    Андрей, приветствую!

    Посмотрел Вашу версию реализации данного паттерна, все очень здорово, спасибо!

    Однако, у меня есть 2 вопроса:

    1. Почему за состояние авторизации пользователя в приложении отвечает ItemsListController? (собственно, свойство authCheck). Более того, переход к модулю авторизации происходит в переопределенном методе viewDidAppear (т.е. показали список айтемов на миллисеекунду, потом поняли, что "нет, чувак, ты не авторизован", и дернули опциональный метод authNeed). Я понимаю, что это пример, но все же (по моему скромному мнению) именно координатор отвечает за состояние приложения, причем конкретно за авторизацию - ApplicationCoordinator.

    2. Утечки памяти. Собственно, на днях, в очередной раз рассматривая различные реализации сего паттерна, отметил для себя удивительную вещь: паттерн неимоверно увеличивает риск утечек памяти. В каждой реализации я видел утечки. Собственно, и у Вас тоже :) Пруф:

    2017-03-03 22 58 13

    Последовательность была такая: Auth -> SignUp -> Terms -> Check -> SignUp -> Auth, и собственно, за 5 итераций убежало 2 МБ. Понятно, что 2 МБ при нынешних ресурсах - это ничто, но там и на экранах по одному UILabel, и будь это что-то посерьезнее, то утечки бы исчислялись уже в десятках, а потом и сотнях мегабайт.

    Так вот, вопрос, как вы с этим справляетесь? - я не имею ввиду процесс оптимизации работы с памятью (с Instruments->Allocations наперевес) :) я имею ввиду, что ссылки на UINavigationController'ы захватываются всеми, кому не попадя, например, при прокидывании последнего во вложенные координаторы (2го, 3го порядка, и т.д.). Т.е. как правильно останавливать работу координатора, чтобы после этого не оставить нерелизнутые объекты?

    Спасибо!

    opened by remenkoff 2
  • Fix a possible ARC bug & Dependency-cleaning upgraded

    Fix a possible ARC bug & Dependency-cleaning upgraded

    After I've played with this solution in local I've found that the TabbarCoordinator leaks because of the handler blocks. After I've found that the removeDependency only clears the top level parent if it has children.

    ps.: Thanks for the opportunity to be my first pull-request ;)

    opened by chosa91 1
  • Fix RouterMockImp

    Fix RouterMockImp

    @AndreyPanov

    RouterMockImp does not confirm to protocol Router, so I implement setRootModule(_:hideBar:) and it pass the Test.

    And your sample app is very helpful for me. Thank you.

    opened by ysk-tngc 1
  • Router and Navigation stack

    Router and Navigation stack

    Firstly definitely one of the best article series on Coordinator Pattern topic!

    Secondly I have one question. Why does not the router maintain the navigationStack? I think if you keep the stack view you can test your RouterImp more easy.

    opened by rafael-silva 0
  • Enum protocol

    Enum protocol

    • все взаимодействие через протоколы
    • у координатора появился отдельный роутер
    • фабрики помимо создания инжектят данные и возвращают tuple с нужными протоколами
    opened by AndreyPanov 0
  • Module/Coordinator leak on modal dismiss

    Module/Coordinator leak on modal dismiss

    If you summon a flow as a modal, and dismiss it by swiping down, the view and its corresponding coordinator remain in memory. In your app, if you click them + button to Create Item and then swipe down to dismiss that screen, instead of tapping "Hide", and do that several times, and then inspect the Object Graph, you'll see that you have multiple ItemCreateCoordinator/Controller/View open.

    You have to adapt Route to UIAdaptivePresentationControllerDelegate somehow.

    opened by adudenamedruby 0
  • Extending Router

    Extending Router

    Kudos for putting together. (This is not an issue but a recommendation per say)

    I think it would be useful if the following methods were part of Router's default implementation. It becomes vital when presenting new flows on top of existing flows.

    func presentOnVisibleModule(_ module: Presentable?, animated: Bool) {
          guard let controller = module?.toPresent() else { return }
          rootController?.visibleViewController?.present(controller, animated: animated, completion: nil)
    }
        
    func dismissFromVisibleModule(animated: Bool) {
          rootController?.visibleViewController?.dismiss(animated: animated, completion: nil)
    }
    
    opened by LamourBt 0
  • Deep link as source of memory leaks

    Deep link as source of memory leaks

    Hey! It seems that ApplicationCoordinator's func start(with option: DeepLinkOption?) is a source of memory leaks in case when we have some coordinators tree and some DeepLink action triggers this start function. No one child coordinator (and view controllers it holds) will be released, but the new ones will be created.

    opened by maxkazakov 0
  • Navigation via back button leak

    Navigation via back button leak

    In your RouterImp, you store a Dictionary of completions with the ViewController as the key. I think that there is a potential leak here.

    The leak will occur when you're using a Navigation Bar's back button instead of programmatically popping the view controller. The Router's dictionary still maintains a reference to the view controller because the back button is not hooked to the router.

    opened by jcyu0208 1
  • BaseCoordinator doubt

    BaseCoordinator doubt

    Hi, I want to create a nice architecture for a project I'm working on and I found your example on the use of coordinators.

    I just started as a Junior Developer and I have some questions if you have the time to answer them:

    • Why not include in the BaseCoordinator the router, coordinatorFactory and moduleFactory as optionals? This way you can inherit them in all coordinators, use the ones you need and leave the others with nil value.

    This is an example of the implementation I suggest:

    Coordinator protocol:

    protocol Coordinator: AnyObject {
        
        var router: Router? { get }
        var coordinatorFactory: CoordinatorFactory? { get }
        var moduleFactory: ModuleFactoryImp? { get }
        
        func start()
    }
    

    BaseCoordinator class:

    class BaseCoordinator {
        
        private(set) var children: [Coordinator] = []
        
        var router: Router?
        var coordinatorFactory: CoordinatorFactory?
        var moduleFactory: ModuleFactory?
        
        init(router: Router?, coordinatorFactory: CoordinatorFactoryImp?, moduleFactory: ModuleFactoryImp?) {
            self.router = router
            self.coordinatorFactory = coordinatorFactory
            self.moduleFactory = moduleFactory
        }
    
        open func start() {
            fatalError("This function must be overriden")
        }
        
        final func add(child: Coordinator) {
            ... [Implementation]
        }
        
        final func remove(child: Coordinator) {
            ... [Implementation]
        }
    }
    

    Then, all coordinators would be simplified because they don't need initializers, they just inherit the one from Coordinator. Finally, an example on how to initialize a Coordinator from the CoordinatorFactory:

    AuthCoordinator(router: router, coordinatorFactory: nil, moduleFactory: ModuleFactoryImp())
    
    ItemCoordinator(router: router(navController), coordinatorFactory: CoordinatorFactoryImp(), moduleFactory: ModuleFactoryImp())
    
    ItemCreateCoordinator(router: router, coordinatorFactory: nil, moduleFactory: ModuleFactoryImp())
    

    For the special case of the TabBarCoordinator:

    class TabbarCoordinator: BaseCoordinator {
      
      private let tabbarView: TabbarView
      
      init(tabbarView: TabbarView, coordinatorFactory: CoordinatorFactoryImp) {
        self.tabbarView = tabbarView
        super.init(router: nil, coordinatorFactory: coordinatorFactory, moduleFactory: nil)
      }
    
      [...]
    }
    
    // Initialization:
    TabbarCoordinator(tabbarView: controller, coordinatorFactory: CoordinatorFactoryImp())
    

    I would appreciate if you could explain me the advantages of doing it your way instead of this way. Thank you in advance!!

    opened by 1397v 0
Sample iOS app demonstrating Coordinators, Dependency Injection, MVVM, Binding

iOS Sample App Sample iOS app written the way I write iOS apps because I cannot share the app I currently work on. Shown concepts Architecture concept

Igor Kulman 632 Dec 28, 2022
iCloud Drive is Apple's essential connection between all your devices, Mac, iPhone, iPad, even your Windows PC.

iCloud Drive is Apple's essential connection between all your devices, Mac, iPhone, iPad, even your Windows PC.While the cost of storage seems expensive in comparison to other online storage services, its advantage is that it works natively across all your devices.

MindInventory 12 Jul 29, 2022
Essential Feed App – Image Feed Feature

Essential Feed App – Image Feed Feature

Alexandre Gravelle 0 Jan 9, 2022
Essential Feed Course

Essential Feed App – Image Feed Feature BDD Specs Story: Customer requests to see their image feed Narrative #1 As an online customer I want the app t

Sebastián León 0 Nov 25, 2021
Simple iOS app to showcase navigation with coordinators in SwiftUI + MVVM.

SimpleNavigation Simple iOS app to showcase the use of the Coordinator pattern using SwiftUI and MVVM. The implementation is as easy as calling a push

Erik Lopez 7 Dec 6, 2022
IOS-Swift-Notes-EssentialDeveloper-Mentoring - Live Mentoring Essential Developer about Composition Root

iOS-Swift-Notes-EsentialDeveloper-Mentoring Problem How to manage massive compos

Arifin Firdaus 1 Jun 1, 2022
RealmSwift, MVVM with Repository, DI, Coordinators,

Todo_App_With_RealmDB Lol, just playing around because I'm bored ?? Refreshing usage of Realm, been a while. New updates look cool though (first time

Emmanuel Omokagbo 2 Aug 4, 2022
A guide on setting up Xcode with all the essential Applications, Tools, and Frameworks to make your development experience with Xcode great!

A guide on setting up Xcode with all the essential Applications, Tools, and Frameworks to make your development experience with Xcode great!

Michael Royal 24 Jan 4, 2023
Swift, UIkit, Anchorage, Clean Architecture, UICollectionViewDiffableDataSourcem, MVVM+Coordinators patterns

About the app iOS project realized with Swift, UIkit, Anchorage, Clean Architecture, UICollectionViewDiffableDataSource and MVVM + Coordinators patter

Luca Berardinelli 4 Dec 29, 2022
Awesome tool for create tutorial walkthrough or coach tour

AwesomeSpotlightView is a nice and simple library for iOS written on Swift 5. It's highly customizable and easy-to-use tool. Works perfectly for tutor

Alexander Shoshiashvili 309 Jan 5, 2023
🤨 Apple Push Notification service tutorial

APNsTutorial-iOS ?? Apple Push Notification service tutorial 단순히 순서를 따라서 가면 될 줄 알았는데 알아야할 것도 있었고 경우에 따라서 요구하는 파일도 달랐다. 그러니 천천히 읽어주시기 바랍니다. 먼저 어떤 서버 환경

Hyungyu Kim 11 Dec 28, 2022
This is a sample project that supplements the tutorial written over at my blog on 'Building a music recognization app in SwiftUI with ShazamKit'

Shazam-Kit-Tutorial This is a sample project that supplements the tutorial written over at my blog on 'Building a music recognization app in SwiftUI w

Swapnanil Dhol 7 Mar 17, 2022
🥺Pinterest Layout Tutorial

PinterestTutorial-iOS ?? Pinterest Layout Tutorial 이미지 크기에 따라서 동적으로 셀의 레이아웃을 설정하는 핀터레스트 레이아웃 구현해 보았다. 완성 코드 UICollectionViewDelegateFlowLayout 의 서브클래스

Hyungyu Kim 6 May 10, 2022
Tutorial GraphQL + Node Express + MySQL, and sample for Android / iOS client

GraphQL-tutorial Tutorial for GraphQL + Node Express + MySQL, and sample for Android / iOS client Blog NeoRoman's GraphQL-tutorial (Korean) Materials

Henry Kim 4 Oct 20, 2022
🏁 make QRcode and QRcode Reader Tutorial

QRCodeReaderTutorial-iOS ?? make QRcode and QRcode Reader Tutorial QR코드와 리더기를 만드는 오픈 라이브러리가 있지만 자체 라이브러리를 활용해서 만들어보기로 했다. 목차 QR코드 만들기 QR코드 Reader 만들기

Hyungyu Kim 5 Dec 9, 2021
👷‍♀️ login tutorial using Kakao iOS SDK

KakaoLoginTutorial-iOS ??‍♀️ login tutorial using Kakao iOS SDK 목차 디자인 가이드 설정단계 애플리케이션 등록 CocoaPods 통해 모듈 설치 Info.plist 설정 초기화 시작하기 전 카카오톡으로 로그인 기본 웹

Hyungyu Kim 8 Dec 3, 2022
A tutorial for using Lottie animation with watchOS in SwiftUI

A tutorial for using Lottie animation with watchOS in SwiftUI

Evandro Harrison Hoffmann 7 Sep 19, 2022
GoodAsOldPhones is the demo app of Swift tutorial on code school.

GoodAsOldPhones GoodAsOldPhones is the demo app of Swift tutorial on code school. This app demonstates basic use and implementation of tab bar control

Kushal Shingote 5 May 24, 2022
Starter Project for YT tutorial

Hey there Foodee is an app for food lovers build in SwiftUI. The YT tutorials can be found on my channel. Figma and bonus video on my Patreon page. Ab

Gary Tokman 16 Dec 13, 2022
TDD-Albums-II is a new hands-on tutorial for iOS engineers learning Test-Driven Development.

TDD-Albums-II Welcome! The TDD-Albums-II tutorial is a sequel to the original TDD-Albums library from 2015. The TDD-Albums library started as a hands-

Rick van Voorden 142 Nov 13, 2022