-
-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coordinator and FlowController #106
Comments
Nice post! I think it would make a little more sense if you explain how it integrates with the AppDelegate before explaining how the AppCoordinator works. |
I wrote a Coordinator-based framework with a demo project which also explains some of the concepts in it's README, including how it integrates with the AppDelegate. Feel free to check it out: https://github.com/Flinesoft/Imperio We are already successfully using it in our projects, thank you @khanlou for your great blog posts which pointed us into the right direction. 👍 |
Awesome article, and hit me right while I was researching Coordinators. BUT... I still have a similar issue I have with other coordinator approaches. Suppose I present a flow controller (which contains a navigation controller, and some VC doing stuff), and suddenly one of its child VCs calls I could implement I have asked the question on SO as well, but leaving away the coordinator part: |
@fabb Hi, the recommended way is that the presented The other workaround I think, is to listen to |
@onmyway133 Yeah, unfortunately my presented |
I have some other questions. Let's say I have a tab bar based UI with navigation controllers in each tab. Let's visualize it with some sprinkled flow controllers:
Some notes to this:
Question 1: Does this make sense until now, would you structure your app similarly? So now for the more contrived question. Let's say Question 2: Whom would you delegate the popping back to I have several ideas for doing it in a clean encapsulated fashion, but I'm not sure which one to choose or if there is an even better one.
Which way would you choose? Maybe something completely different? |
And on it goes with the questions/findings, sorry for spamming guys ;-)
I have found that instead of dedicated
The method
I also like to sprinkle
When I added flow controllers as children of the UITabBarController, I noticed that my tabbar items did not appear, as I previously have only set them on the UINavigationController. Now they need to be set on the flow controller instead. Unfortunately this has to be done before
Or alternatively, If you like delegation:
I'm sure there will be a few more methods that need forwarding in flow controllers.
You know, the ones deemed "uncool" after autolayout appeared? The one you know all too well from
As Peter Steinberger pointed out, when we create Swift extensions to an ObjC object, we should still prefix them, as we might run into strange issues. Also I don't like how
Or just make it unavailable in ObjC, should do the trick too if your app project is not convoluted by too much ObjC cruft:
UIKit does a little automagic here and there. The problem with that is that it might break due to small changes and not be immediately possible but introduce hard to find bugs. Naughty naughty...
There might be more issues hidden like this in UIKit. |
And a nice way to return the next parent flow controller without having to explicitly set it. I think both the explicit and implicit ways have their perks.
This searches through the responder chain until it finds an object that conforms to the needed protocol. This even works when starting at a UIView. WARNING: When you call this from a VC that has been presented, it might not work as expected. The responder chain, starting from the presented VC, will contain all VCs up to the presented VC, then as next responder return the root parent VC of the VC that presented (and if that was presented itself, it's next responder will in turn return the root parent VC that presented that VC) and continue normally from there. It's caused due to the strange nature what |
If I have main flow like this :
I set the self.tabBarController.viewControllers = [feedFlowController, profileFlowController, settingsFlowController] I expect to hide tab bar when extension FeedFlowController: FeedViewControllerDelegate {
func showDetail(_ detail: String) {
let feedDetail = FeedDetailViewController(detail, dependencyContainer: self.dependencyContainer)
feedDetail.hidesBottomBarWhenPushed = true
embeddedNavigationController.pushViewController(feedDetail, animated: true)
}
} So, do I have to give the embedded navigation controller of sub flow controller to the @onmyway133 Can you help me to understand? Or a better way to solve this? |
Seems like |
@fabb Yes, we have to be careful about the magic hidden in |
@calvingit I stepped a bit through the disassembly. I think we cannot really fix
Unfortunately when we use a flow controller as container around that The only ugly hack I could think of would be to use a custom subclass of |
The „sequence vs. UI“ categorization of VCs reminds me of this React article which differentiates between „Presentational“ and „Container“ components: |
I think using the responder chain is a more elegant approach : https://augmentedcode.io/2018/11/18/navigating-using-flow-controllers-and-responder-chain-on-ios/ |
Are there any updates on solving the hideBottomBarWhen pushed using FlowControllers? |
Every new architecture that comes out, either iOS or Android, makes me very excited. I'm always looking for ways to structure apps in a better way. But after some times, I see that we're too creative in creating architecture, aka constraint, that is too far away from the platform that we're building. I often think "If we're going too far from the system, then it's very hard to go back"
I like things that embrace the system. One of them is Coordinator which helps in encapsulation and navigation. Thanks to my friend Vadym for showing me
Coordinator
in action.The below screenshot from @khanlou 's talk at CocoaHeads Stockholm clearly says many things about
Coordinator
But after reading A Better MVC, I think we can leverage view controller containment to do navigation using
UIViewController
only.Since I tend to call view controllers as
LoginController, ProfileController, ...
and the termflow
to group those related screens, what should we call aCoordinator
that inherits fromUIViewController
🤔 Let's call itFlowController
😎 .The name is not that important, but the concept is simple.
FlowController
was also inspired by this Flow Controllers on iOS for a Better Navigation Control back in 2014. The idea is from awesome iOS people, this is just a sum up from my experience 😇So
FlowController
can just aUIViewController
friendly version ofCoordinator
. Let see howFlowController
fits better intoMVC
1. FlowController and AppDelegate
Your application starts from
AppDelegate
, in that you setupUIWindow
. So we should follow the same "top down" approach forFlowController
, starting withAppFlowController
. You can construct all dependencies that your app need forAppFlowController
, so that it can pass to other childFlowController
.AppDelegate
is also considered Composition RootHere is how to declare
AppFlowController
inAppDelegate
Here are some hypothetical
FlowController
that you may encounterUIPageViewController
and maybe ask for some permissionsUINavigationController
to show login, sms verification, forget password, and optionally startSignUpFlowController
UITabBarController
with each tab serving main featuresFlowController
chain.The cool thing about
FlowController
is it makes your code very self contained, and grouped by features. So it's easy to move all related things to its own package if you like.2. FlowController as container view controller
Basically,
FlowController
is just a container view controller to solve thesequence
, based on a simple concept calledcomposition
. It manages many child view controllers in its flow. Let' say we have aProductFlowController
that groups together flow related to displaying products,ProductListController
,ProductDetailController
,ProductAuthorController
,ProductMapController
, ... Each can delegate to theProductFlowController
to express its intent, likeProductListController
can delegate to say "product did tap", so thatProductFlowController
can construct and present the next screen in the flow, based on the embeddedUINavigationController
inside it.Normally, a
FlowController
just displays 1 childFlowController
at a time, so normally we can just update its frame3. FlowController as dependency container
Each view controller inside the flow can have different dependencies, so it's not fair if the first view controller needs to carry all the stuff just to be able to pass down to the next view controllers. Here are some dependencies
Instead the
FlowController
can carry all the dependencies needed for that whole flow, so it can pass down to the view controller if needed.Here are some ways that you can use to pass dependencies into
FlowController
4. Adding or removing child FlowController
Coordinator
With
Coordinator
, you need to keep an array of childCoordinators
, and maybe use address (===
operator) to identify themFlowController
With
FlowController
, since it isUIViewController
subclass, it hasviewControllers
to hold all those childFlowController
. Just add these extensions to simplify your adding or removing of childUIViewController
And see in action how
AppFlowController
work with addingand with removing when the child
FlowController
finishes5. AppFlowController does not need to know about UIWindow
Coordinator
Usually you have an
AppCoordinator
, which is held byAppDelegate
, as the root of yourCoordinator
chain. Based on login status, it will determine whichLoginController
orMainController
will be set as therootViewController
, in order to do that, it needs to be injected aUIWindow
You can guess that in the
start
method ofAppCoordinator
, it must setrootViewController
beforewindow?.makeKeyAndVisible()
is called.FlowController
But with
AppFlowController
, you can treat it like a normalUIViewController
, so just setting it as therootViewController
6. LoginFlowController can manage its own flow
Supposed we have login flow based on
UINavigationController
that can displayLoginController
,ForgetPasswordController
,SignUpController
Coordinator
What should we do in the
start
method ofLoginCoordinator
? Construct the initial controllerLoginController
and set it as therootViewController
of theUINavigationController
?LoginCoordinator
can create this embeddedUINavigationController
internally, but then it is not attached to therootViewController
ofUIWindow
, becauseUIWindow
is kept privately inside the parentAppCoordinator
.We can pass
UIWindow
toLoginCoordinator
but then it knows too much. One way is to constructUINavigationController
fromAppCoordinator
and pass that toLoginCoordinator
FlowController
LoginFlowController
leveragescontainer view controller
so it fits nicely with the wayUIKit
works. HereAppFlowController
can just addLoginFlowController
andLoginFlowController
can just create its ownembeddedNavigationController
.7. FlowController and responder chain
Coordinator
Sometimes we want a quick way to bubble up message to parent
Coordinator
, one way to do that is to replicateUIResponder
chain usingassociated object
and protocol extensions, like Inter-connect with CoordinatorFlowController
Since
FlowController
isUIViewController
, which inherits fromUIResponder
, responder chain happens out of the box8. FlowController and trait collection
FlowController
I very much like how Kickstarter uses trait collection in testing. Well, since
FlowController
is a parent view controller, we can just override its trait collection, and that will affect the size classes of all view controllers inside that flow.As in A Better MVC, Part 2: Fixing Encapsulation
From setOverrideTraitCollection
9. FlowController and back button
Coordinator
One problem with
UINavigationController
is that clicking on the defaultback button
pops the view controller out of the navigation stack, soCoordinator
is not aware of that. WithCoordinator
you needs to keepCoordinator
andUIViewController
in sync, add try to hook upUINavigationControllerDelegate
in order to clean up. Like in Back Buttons and CoordinatorsOr creating a class called
NavigationController
that inside manages a list of child coordinators. Like in Navigation coordinatorsFlowController
Since
FlowController
is just plainUIViewController
, you don't need to manually manage childFlowController
. The childFlowController
is gone when you pop or dismiss. If we want to listen toUINavigationController
events, we can just handle that inside theFlowController
10. FlowController and callback
We can use
delegate
pattern to notifyFlowController
to show another view controller in the flowAnother approach is to use
closure
as callback, as proposed by @merowing_, and also in his post Improve your iOS Architecture with FlowControllers11. FlowController and deep linking
TBD. In the mean while, here are some readings about the UX
The text was updated successfully, but these errors were encountered: