Coordinator : the Pattern · the Library · the Class · recommended Implementation
Per this library, Coordinator instance is essentially defined by these two points:
· 1 · It has one instance of UIViewController
which is its root ViewController. This is usually some container controller like UINavigationController
but it can be any subclass.
This way, it can internally create instances of UIVC, populate their input with data it needs and then just show or present them as needed. By reusing these essential UIKit mechanisms it minimally interferes with how iOS already works.
Coordinator takes care of navigation and routing while View Controller takes care of UI controls, touches and their corresponding events.
· 2 · It subclasses UIResponder
, same as UIView
and UIViewController
do.
This is crucial. Library extends UIResponder by giving it a new property called coordinatingResponder
. This means that if you define a method like this:
extension UIResponder {
@MainActor
@objc func accountLogin(username: String,
password: String,
sender: Any?) async throws
{
try await coordinatingResponder?.accountLogin(
username: username,
password: password,
sender: sender
)
}
}
you can
- Call
accountLogin()
from anywhere: view controller, view, button's event handler, table/collection view cell, UIAlertAction etc. - That call will be passed up the responder chain until it reaches some Coordinator instance which overrides that method. It none does, it gets to
UISceneDelegate
/UIApplicationDelegate
(which is the top UI point your app is given by iOS runtime) and nothing happens. - At any point in this chain you can override this method, do whatever you want and continue the chain (or not, as you need)
There is no need for Delegate pattern (although nothing stops you from using one). No other pattern is required, ever.
By reusing the essential component of UIKit design — the responder chain — UIViewController's output can travel through the…
This is all that’s required for the chain to work:
public extension UIResponder {
@objc public var coordinatingResponder: UIResponder? {
return next
}
}
That bit covers all the UIView
subclasses: all the cells, buttons and other controls.
Then on UIViewController
level, this is specialized further:
extension UIViewController {
override open var coordinatingResponder: UIResponder? {
guard let parentCoordinator = self.parentCoordinator else {
guard let parentController = self.parent else {
guard let presentingController = self.presentingViewController else {
return view.superview
}
return presentingController as UIResponder
}
return parentController as UIResponder
}
return parentCoordinator as? UIResponder
}
}
Once responder chain moves into UIViewController instances, it stays there regardless of how the UIVC was displayed on screen: pushed or presented or embedded, it does not matter.
Once it reaches the Coordinator’s rootViewController
then the method call is passed to the parentCoordinator
of that root VC.
extension UIViewController {
public weak var parentCoordinator: Coordinating? {
get { ... }
set { ... }
}
}
So this is how the chain is closed up. Which brings us to the Coordinator
class.