14

[Swift 5] Introduction To Coordinator Design Pattern in iOS

 3 years ago
source link: https://sweettutos.com/2020/05/18/swift-5-introduction-to-coordinator-design-pattern-in-ios/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[Swift 5] Introduction To Coordinator Design Pattern in iOS

If you develop on iOS for a while, you surely heard about the Coordinator pattern. What I like the most about it is that I don’t have to change the traditional MVC architecture entirely but instead enhance it with the Coordinator pattern to make my view controller as light as possible and ensure more clarity to my code in addition to a nice separation of concerns.

In this quick tutorial you will learn how a view controller communicate with its relevant Coordinator and delegate to it the event handling task so that the view controller can focus on what matter the most to it: User Interface.

First download the starter project here and open it with Xcode.

Run the project to explore what it is all about, the structure is pretty simple, there is two view controllers embedded into a navigation controller, on button click, you show the details screen.

The navigation controller is initialized in the AppDelegate didFinishLaunchingWithOptions method.

What you notice is that the navigation stuff is done on the view controller layer, that means the view controller knows exactly what to do on button click and even how to do it (push to navigation controller in this case).

Isn’t that strange? You may wonder how can this be bad, well this will become a hassle prone once your project gets bigger and when you will need to query the server for data and persist it locally, such tasks will be done in the view controller, right?

This way the view controller responsibilities will include:

  • UI setup
  • Networking
  • Data persistence

Well, I ensure you will get bored with such MassiveViewController 🙂

Time to change this!

The Coordinator pattern is nothing but a tiny protocol with one required method.

Create a new Swift file (File menu -> New -> File -> Swift File), name it Coordinator and declare the following protocol inside:

protocol Coordinator {
    func start()

That’s it!! Now in order to use the Coordinator pattern, your class need to conform to the Coordinator protocol.

You will create a Coordinator that manage the first screen and another one for the details screen. From the menu create a new class file (File menu -> New -> File -> Swift File) and name it HomeCoordinator.

Place the following code inside:

class HomeCoordinator: Coordinator {
    func start() {

Usually you use the start method to instantiate the view controller that is backed with this Coordinator. However, the home screen is currently instantiated without relying on the Coordinator factory.

So let’s change this to make the Coordinator load the Home screen instead.

From the Project navigator, select the AppDelegate.swift file and change the implementation of its didFinishLaunchingWithOptions delegate method to the following:

let window = UIWindow(frame: UIScreen.main.bounds)
let homeCoordinator = HomeCoordinator(window: window)
self.window = window
self.homeCoordinator = homeCoordinator
homeCoordinator.start()
return true

//1 As you can see, you now take care manually of the home screen loading, you pass the window reference as a dependency to the home Coordinator in order to use it later on to load the view, passing the window to the Coordinator this way is called Dependency Injection or DI shortly.

//2 Here you call the start method which, as its name tells, will start loading the view on the screen.

Always in the AppDelegate, add the following property declaration at the top of the file, right after the class opening bracket.

var homeCoordinator: HomeCoordinator?

Now switch to the HomeCoordinator.swift, make sure to import UIKit at the top of the file, and change its implementation to the following:

private let window: UIWindow
private let rootViewController: UINavigationController
init(window: UIWindow) {
   self.window = window
   self.rootViewController = UINavigationController()
func start() {
   window.rootViewController = rootViewController
   let homeViewController = HomeViewController(nibName: nil, bundle: nil)
   rootViewController.pushViewController(homeViewController, animated: true)
   window.makeKeyAndVisible()

//1 Here you declare the stored properties you need

//2 The initializer is the best place to assign the dependencies of your object, you take advantage of it to initialize the navigation controller that will be used in the start method as the presenter of the view controller.

//3 Here you will assemble the puzzle, you basically embed the home view controller in the navigation stack, assign the latter as the root of the window and kick it on the screen :]

Build and run the app, the home screen should render correctly ;]

Click the button and you should be able to move to the next screen as you used to do earlier.

As discussed earlier, the HomeViewController is in charge of deciding how and where to move once you click on the button. You clearly notice this in the moveToNextScreen implementation.

@IBAction func moveToNextScreen(_ sender: Any) {
    let detailsViewController = DetailsViewController(nibName: nil, bundle: nil)
     self.navigationController?.pushViewController(detailsViewController, animated: true)

You are going to change this and the HomeCoordinator will be responsible of deciding this behavior.

Coordinator – View Controller communication

There is two common ways to make the view controller report user interaction to its relevant Coordinator: Delegation pattern or closure callback.

In this tutorial you are going to use a closure callback.

Select the HomeViewController.swift and declare a callback as following:

var moveToNextScreenHandler: (() -> Void)?

Now replace the implementation of the moveToNextScreen action method to look like this:

@IBAction func moveToNextScreen(_ sender: Any) {
   moveToNextScreenHandler?()

Now move back to the HomeCoordinator.swift file and add the following code inside the start method (right after you instantiate the home view controller):

homeViewController.moveToNextScreenHandler = {

At this stage, the Coordinator is ready to intercept the user action. You can add a print statement inside the closure body and run the app to verify it. Good work so far ;]

The entry point for each screen/view controller should be through its Coordinator, in this case, you want to display the details screen. You can easily instantiate the details view controller and use the root view controller to push it to the navigation stack and show it to the user.

However, we want to keep things clean and delegate all this to the details Coordinator instead.

Let’s add the Coordinator class that will handle this, select File- > New -> File from the menu and choose Swift File from the template panel, name the file DetailsCoordinator and save it to the project target.

Open the newly created file and change its content to the following:

import UIKit
public class DetailsCoordinator: Coordinator {
    private let presentingViewController: UINavigationController
    init(presentingViewController: UINavigationController) {
        self.presentingViewController = presentingViewController
    func start() {
        let detailsViewController = DetailsViewController(nibName: nil, bundle: nil)
        presentingViewController.pushViewController(detailsViewController, animated: true)

//1 The stored property called presentingViewController will be injected from the HomeCoordinator, make sense, right?
//2 You instantiate the details view controller and push it to the presenting view controller navigation stack

Now you will finish up by calling the DetailsCoordinator start method from the HomeCoordinator to kick off the details screen ;]

Move back to the HomeCoordinator.swift file and change the moveToNextScreenHandler closure body to look like this:

homeViewController.moveToNextScreenHandler = {[weak self] in
     guard let rootViewController = self?.rootViewController else {
          fatalError("rootViewController is nil")
     let coordinator = DetailsCoordinator(presentingViewController: rootViewController)
     coordinator.start()

Nothing fancy here, you basically instantiate the DetailsCoordinator, the rootViewController being the only dependency you sent to its initializer, then you simply call the factory method start which will instantiate the details view controller and show it on the screen.

That’s it! Run the project and enjoy your work ;]

Where to go from here?

You can download the final project here.

In this tutorial, you just learned :
– How to use Coordinator
– How a Coordinator can make your code respect the Single responsibility principle by delegating navigation work from the view controller to its Coordinator and keep the view controller focus on user interaction interception.
– How to use Dependency Injection and how this make you code clear and transparent.

For more details, I recommend you watch the excellent presentation of the Coordinator pattern here.

What Architectural pattern you are confortable to use in your project? Let me know in the comments section below :]

If you enjoy this post, you can subscribe in the form below to get notified of all our upcoming tutorials, ebooks, apps and discounts.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK