-
-
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() | |
} | |
} |
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?
Mmmh… In that kind of code:
I don't think we can use
[unowned self]
in the closure, asself
here is thestruct Coordinator
so as astruct
it's a value type and can't be markedunowned
(doesn't have any sense for anything non-reference type)But we can't either
[unowned window]
as thewindow
isn't even referenced / captured in that closure (as we point the closures toprivate 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 likewindow: 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 useunowned
on astruct
?Will definitely have to try that in an actual playground at some point…