-
-
Save AliSoftware/6d2d146f7baccb0099cc to your computer and use it in GitHub Desktop.
struct Coordinator { | |
let window: UIWindow | |
let navCtrl: UINavigationController? | |
func start() { | |
presentWelcomeScreen() | |
} | |
private func presentWelcomeScreen() { | |
let vc = WelcomeScreenViewController() // Instanciate from code, XIB, Storyboard, whatever your jam is | |
vc.navTransitions = WelcomeScreenTransitions( | |
showHome: self.pushHomeScreen, // see private func below | |
showCreateAccountScreen: self.pushCreateAccountScreen | |
) | |
window.rootViewController = vc // or whatever | |
} | |
private func pushHomeScreen(user: User?) { | |
let vc = HomeScreenViewController() // Instanciate from code, XIB, Storyboard, whatever your jam is | |
vc.user = user // or inject a ViewModel here if you're doing MVVM, you get the idea | |
vc.navTransitions = HomeScreenTransitions( | |
showUserInfoScreen: { | |
// From Home, we *push* the UserInfoVC | |
self.navigationController.pushViewController(instanciateUserInfoScreen(user), animated: true, completion: nil) | |
} | |
showProductDetail: self.pushProductDetail // extracted in private function below | |
disconnectAndShowLoginScreen: self.presentLoginScreen // same | |
) | |
navCtrl = UINavigationController(rootViewController: vc) | |
window.rootViewController = navCtrl | |
} | |
private func pushProductDetail(product: Product) { | |
let vc = ProductDetailViewController() // Instanciate from code, XIB, Storyboard, whatever your jam is | |
vc.product = product // inject any object to the VC, like a ViewModel or whatever | |
vc.navTransitions = ProductDetailTransitions( | |
showUserInfoScreen: { | |
// But from ProductDetail VC, we *present* the UserInfoVC *modally* | |
self.presentViewController(instanciateUserInfoScreen(product.owner), animated: true, completion: nil) | |
} | |
back: { navCtrl?.popViewController(animated: true) } | |
) | |
navCtrl?.pushViewController(vc, animated: true) { _ in } | |
} | |
private func pushCreateAccountScreen() { | |
// Create and push a CreateAccountViewController and its CreateAccountTransitions struct | |
... | |
} | |
private func instanciateUserInfoScreen(user: User) -> UIViewController { | |
// Create, configure and return an UserDetailsViewController | |
... | |
} | |
} |
struct HomeScreenTransitions { | |
let showUserInfoScreen: Void -> Void | |
let showProductDetail: Product -> Void | |
let disconnectAndShowLoginScreen: Void -> Void | |
} | |
class HomeScreenViewController: UIViewController { | |
var navTransitions: HomeScreenTransitions! | |
@IBAction func profileAction(_: AnyObject) { | |
navTransitions.showUserInfoScreen() | |
} | |
@IBAction func disconnectAction(_: AnyObject) { | |
confirm("Are you sure?") { | |
navTransitions.disconnectAndShowLoginScreen() | |
} | |
} | |
@objc func tableView(tableView: UITableView, didSelectRowAtIndexPath: NSIndexPath) { | |
let product = productsDataSource[indexPath.row] | |
navTransitions.showProductDetail(product) | |
} | |
} |
struct Product { | |
let id: Int | |
let owner: User | |
... | |
} | |
struct ProductDetailTransitions { | |
let showUserInfoScreen: Void -> Void | |
let back: Void -> Void | |
} | |
class ProductDetailViewController: UIViewController { | |
var navTransitions: ProductDetailTransitions! | |
@IBAction func showProductOwner(_: AnyObject) { | |
navTransitions.showUserInfoScreen() | |
} | |
... | |
} |
struct WelcomeScreenTransitions { | |
let showHome: User? -> Void | |
let showCreateAccountScreen: Void -> Void | |
} | |
class WelcomeScreenViewController: UIViewController { | |
var navTransitions: WelcomeScreenTransitions! | |
var webService: WebServiceAPI! | |
@IBAction func loginAction(_: AnyObject) { | |
webService.login(userTextField.text, passwordTextField.text) { (user: User?) in | |
navTransitions.showHome(user) | |
} | |
} | |
@IBAction func createAccountAction(_: AnyObject) { | |
navTransitions.showCreateAccountScreen() | |
} | |
} |
Remember to check if Coordinator -> Window -> VC -> closure that captures coordinator that has a Window inside
causes a retain cycle or not ;)
yeah I think we may need to add [unowned window]
in those closures to break the reference cycle, good point 👍
Mmmh… In that kind of code:
vc.navTransitions = WelcomeScreenTransitions(
showHome: self.pushHomeScreen, // see private func below
showCreateAccountScreen: self.pushCreateAccountScreen
)
I don't think we can use [unowned self]
in the closure, as self
here is the struct Coordinator
so as a struct
it's a value type and can't be marked unowned
(doesn't have any sense for anything non-reference type)
But we can't either [unowned window]
as the window
isn't even referenced / captured in that closure (as we point the closures to private func
tions and not providing the implementations inline)
So I'm a bit puzzled here… is there really a reference cycle here (even with value types, though the struct Coordinator
contains reference types like window: UIWindow
), or is the cycle broken thanks to value types… and if it's not, how to break it as I believe we can't use unowned
on a struct
?
Will definitely have to try that in an actual playground at some point…
Ok there definitely are issues with retain cycles, and a struct
/value type containing reference types doesn't help making things clear on this front.
The right solution might be to make Coordinator
a class
instead of a struct (which actually makes sense, as we don't really want independent copies all over), and be sure then to use [unowned self]
in closures when appropriate.
that's really awesome ! Good job AliSoftware 👽 .
How to do you handle the cas with UITabBarController with severals UIViewController ?
- create and instance the UITabBarController directly in the appCoordinator.
- group all the transitions in the UITabBarController
@remirobert good question never had that use case until know. Since that gist using closure I've finally switched back to the delegates version of that Coordinator pattern anyway, which avoids the risk of hidden retain cycles we have with closures everywhere here
@remirobert @AliSoftware any word or example code on using uitabcontroller with coordinator pattern?
Main concept:
struct
listing all the possible transitions from that state. Like thestruct WelcomeScreenTransitions
which lists all the possible screens to go to from the WelcomeScreenViewControllerstructs
are only containers for closures. They don't carry any implementation details. (They act a bit like a wrapper around a list ofcompletionBlock
, one for each possible outcome / transition)structs
don't have any dependency with theCoordinator
. Separation of concerns is important.Coordinator
is responsible for handling the transition between screens. To do so, each time it needs to present a screen, it is responsible for:UIViewController
…Transitions
struct instance, providing the the implementation detail for each possible action, and pass thatstruct
to the VCCoordinator
is the only one knowing how to present each ViewController, and what are the presenting actions to do for this ViewController for its own next steps (the closures implementations). So all the navigation logic is handled by theCoordinator
alone, and isn't responsible for anything else (except maybe passingViewModels
from VC to VC)Other interesting links on a similar subject: