DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report

How to Use a Coordinator Pattern to Separate Concerns in iOS

This article focuses on navigation and provides an actionable example of using a coordinator pattern to extract navigation flow from view controllers.

Annie Dossey user avatar by
Annie Dossey
·
May. 09, 19 · Tutorial
Like (1)
Save
Tweet
Share
9.31K Views

Join the DZone community and get the full member experience.

Join For Free

Let’s talk about view controllers. Right off the hop, stop making a mess of your view controllers and start putting your code in logical places – view controllers are for views, and views only. It’s very tempting to throw all sorts of logic into a view controller, but when we separate concerns, we write code that’s easier to understand and easier to reuse.

There are handfuls of responsibilities we can dump into a view controller: data fetching, data transformation, user input, model-view binding, model mutation, animations, and navigation flow, just to name a few. But, we shouldn’t house all of these responsibilities in one place; otherwise, we’re working with a confusing and unwieldy view controller.

This article will focus specifically on navigation and provide an actionable example of using a coordinator pattern to extract navigation flow from view controllers. This article will also touch on how protocols facilitate more concise communication between objects.

For this exercise, I teamed up with Clearbridge Mobile iOS Developer, Michael Voline, to learn about separating concerns to create clean, isolated, and reusable view controllers. Credit goes to Michael for writing the code to support this article. If you'd like to skip the step-by-step example, and move right to the full version of the configuration code, there is a GitHub link at the end of the article.

Note: There are several ways you can decouple view controllers; however, using a coordinator pattern to handle the navigation logic that doesn’t belong in a view controller is one of many ways to create abstractions and separate concerns.

Don’t Let View Controllers Know About Other View Controllers

A problem that sometimes comes up when creating view controllers is writing the code in such a way that one view controller must be aware of another. What’s worse is that when view controllers are aware of the position of other view controllers in an app’s flow, they have the authority to configure and present other view controllers. What ends up happening is view controllers get tied together in a chain and the navigation flow is spread across multiple objects.

What if we need to insert more screens into the flow?We would have to write more configuration code inside the view controllers, further perpetuating the problem.

Coordinators in Action

So, let’s jump in with a solution — if we control the app’s flow with a coordinator pattern, view controllers communicate only with the coordinator and not with each other.

The following examples will demonstrate a coordinator pattern that handles the navigation between two very basic view controllers: a login view and sign-up view. Our login view will present the user the option to sign-up and our sign-up view will present the user the option to go back to the login view. For flexibility, we’ll use protocols to set the communication rules for the coordinator to conform to.

First, we’ll set up a Coordinator to control the app’s flow in the app delegate like this:

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {

    private let coordinator = AppCoordinator()

    func application(_ application: UIApplication,...) -> Bool {
        coordinator.start()
        return true
    }
}

Next, let’s write the view controllers for our login view and sign-up view. Here’s the LoginViewController:

protocol LoginViewControllerCoordinatorDelegate: AnyObject {
    func didPressSignupButton()
}

final class LoginViewController: UIViewController {

    weak var delegate: LoginViewControllerCoordinatorDelegate?

    @objc func didPressExitButton() {
        delegate?.didPressSignupButton()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton()
               button.addTarget(self, action: #selector(didPressExitButton), for: .touchUpInside)
              view.addSubview(button)
    }
}

Notice how we’ve set up a LoginViewControllerCoordinatorDelegate protocol. This will help us limit exposure to the AppCoordinator’s methods, which you will see soon.

And, the SignupViewController:

import UIKit

protocol SignupViewControllerCoordinatorDelegate: AnyObject {
    func didPressLoginButton()
}

final class SignupViewController: UIViewController {

    weak var delegate: SignupViewControllerCoordinatorDelegate?

    @objc func didPressButton() {
      delegate?.didPressLoginButton()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton()
        button.addTarget(self, action: #selector(didPressButton), for: .touchUpInside)
        view.addSubview(button)
    }
}

From here, I’d like to draw your attention to the protocols in each view controller.

protocol LoginViewControllerCoordinatorDelegate: AnyObject {
    func didPressSignupButton()
}

What we’ve done here is designed a protocol that limits what the LoginViewController has access to in the Coordinator. There are multiple methods and properties inside the Coordinator that the LoginViewController doesn’t need to know; it only needs to know the didPressSignupButton function.

Similarly, we’ve designed a protocol for the SignupViewController, that again limits the information the view controller can extract from the Coordinator. The SignupViewController only needs to know didPressLoginButton function and nothing else.

protocol SignupViewControllerCoordinatorDelegate: AnyObject {
    func didPressLoginButton()
}

By conforming the Coordinator to each of these protocols, we are able to define exact communication rules about what information needs to be conveyed between the Coordinator and our view controllers.

Our app coordinator will look something like this:

final class AppCoordinator {

    private let window: UIWindow
    private let navigation: UINavigationController

    // Please see sample project at the end for full implementation.
    // init() {...}

    func start() {
        let login = LoginViewController()
        login.delegate = self
        navigation.pushViewController(login, animated: true)
    }
}

// MARK: - LoginViewControllerCoordinatorDelegate
extension AppCoordinator: LoginViewControllerCoordinatorDelegate {
    func didPressLoginButton() {
        navigation.popViewController(animated: true)
    }
}

// MARK: - SignupViewControllerCoordinatorDelegate
extension AppCoordinator: SignupViewControllerCoordinatorDelegate {
    func didPressSignupButton() {
        let signup = SignupViewController()
        signup.delegate = self 
        navigation.pushViewController(signup, animated: true)
    }
}

The AppCoordinator class contains a navigation property that is used to push and pop view controllers from the navigation stack. In this example, we do not use storyboards and hence we configure window manually (we've included a GitHub link for the full version of the configuration code at the end of this article). When we call start() method in AppDelegate, the LoginViewController is pushed onto the navigation stack and the app starts its main flow. 

For convenience, we used extensions to conform the AppCoordinator to our two protocols. As you can see, both LoginViewController and SignupViewController propagate touch events to AppCoordinator and let it decide what happens next in the app flow.

We didn’t create a hard-coded path between our login and sign-up views, rather we’ve extracted navigation logic out of the view controllers and placed it into a separate object responsible for transitioning between screens. Following this pattern helps make it easier to see what’s happening in the flow of the app. Using a coordinator also makes testing easier and allows for reusability.

Case and point, each view controller should only know enough to perform its specific action, and nothing else. Save yourself valuable time and effort – keep your view controllers isolated and don’t overcrowd them with code out of convenience

You can find the full configuration code on GitHub here.

This article was originally published on Clearbridge Mobile here.

mobile app Flow (web browser) Protocol (object-oriented programming)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 19 Most Common OpenSSL Commands for 2023
  • OpenVPN With Radius and Multi-Factor Authentication
  • Journey to Event Driven, Part 1: Why Event-First Programming Changes Everything
  • Choosing the Right Framework for Your Project

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: