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.
The coordinator pattern is 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.
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.
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.
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.
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.
Let’s go through an example project!
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.
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.
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.
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.
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].
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 in the comments below!