How to use the Coordinator pattern with Closures in iOS Programming?
One of the main challenges when creating an iOS application is the Massive View Controller due to Apple’s MVC architecture. There are, however, numerous solutions to this problem, such as using MVVM, MVP or Viper, and not all of them require changing the whole architecture.
What is Coordinator pattern in iOS?
The coordinator pattern a delegate protocol pattern used to improve screen flow logic by moving the logic for opening and showing views into a separate Coordinator class.
It was created by Khanlou.
The Coordinator pattern
Since there are many articles and tutorials on the Coordinator topic which are explaining the pros and cons of the usage, this article will focus on explaining how to implement the communication between ViewController and Coordinator using closures.
What is the best way to communicate between View controllers and Coordinator?
1. Using Delegates
The most common implementation of this communication is by using Delegates. While View Controller defines the protocol and contains the delegate property, the Coordinator implements the required protocol methods.
2. Using the UIResponder
Another interesting implementation is by using the UIResponder. The entire communication between the Coordinator, View Controller, and even UIViews is based on the UIResponder chain. UIResponder chain is used to handle touch events in the app. The request either goes through the chain until the UI component which implements this event is found or it runs through the whole chain.
The main issue with this implementation is that the Coordinator is not a UI component. Thus creating events that are not touches might be strange and confusing.
3. Using Closures
The approach we will focus on is the usage of Closures. Each View Controller defines the closures which the Coordinator uses to navigate between screens. The View Controller defines the closures that are called when a new screen needs to be shown. The Coordinator implements these closures and in them, for example, initializes the new UIViewController and presents it.
How to implement the communication between ViewController and Coordinator - Example
Our example app contains two screens - Home and Profile. The Home screen has two buttons with the actions to push and present the Profile screen. The Profile screen contains basic information for users and the close button which closes the Profile screen.
Project Example
The implementation of the HomeViewController is very simple. We need to define two closures: pushProfileAction and presentProfileAction, and two IBActions that will just call the corresponding closure.
ProfileViewController has a similar implementation - one closure, closeProfileAction, and one IBAction.
var pushProfileAction: (() -> Void)? var presentProfileAction: (() -> Void)? @IBAction private func showProfileButtonTapped(_ sender: Any) { pushProfileAction?() } @IBAction private func presentProfileButtonTapped(_ sender: Any) { presentProfileAction?() }
Before setting the HomeViewController as a root for Navigation Controller, it needs to be instantiated and the closure actions need to be set in the Coordinator Class.
homeViewController.pushProfileAction = { [weak self] in self?.showProfile() } homeViewController.presentProfileAction = { [weak self] in self?.presentProfile() }
As we can see, using Coordinators with closures is very simple - just define closure actions in the View Controller, and implement them in the Coordinator.
Advantages and Disadvantages of working with closures
The advantage of closures over delegates is clear when we have one-to-many relationships between the Coordinator and View Controllers.
Take a look at the sample project again. You will see that the Coordinator class shows multiple ProfileViewControllers differently (both presenting and pushing), with different ways of closing that screen afterwards. Instead of complicating your code with if-statements and passing method arguments, we can simply use different closure implementations.
The disadvantage of working with closures is that you may forget to implement the closure and a potential memory leak could happen if you don’t use [weak self].
Is working with closures really difficult?
Working with closures may look very complex and scary. But, once you get the hang of them, you will be able to simplify your code and avoid some complex problems.
Is there an approach you normally use? Would you give closures a try? Let us know.