βHow does an app transition from a ViewController to another?β. This question is common and puzzling regarding iOS development. There are many answers, as every architecture has different implementation variations. Some do it from the view controller, while some do it using a router/coordinator, which is an object that connects view models.
To better answer the question, we are building RxCoordinator, a navigation framework based on the Coordinator pattern. It's especially useful for implementing MVVM-C, Model-View-ViewModel-Coordinator:
Create an enum with all of the navigation paths for a particular flow. (It is up to you when to create a Route/Coordinator
, but as our rule of thumb, create a new Route/Coordinator
whenever a new navigation controller
is needed.)
enum HomeRoute: Route {
case home
case users
case logout
func prepareTransition(coordinator: AnyCoordinator<HomeRoute>) -> NavigationTransition {
switch self {
case .home:
let viewModel = HomeViewModel(coodinator: coordinator)
let vc = HomeViewController(viewModel: viewModel)
return .push(vc)
case .users:
let viewModel = UsersViewModel(coodinator: coordinator)
let vc = UsersViewController(viewModel: viewModel)
return .push(vc)
case .logout:
return .dismiss()
}
}
}
Setup the root view controller in the AppDelegate.
...
let basicCoordinator = BasicCoordinator<HomeRoute>(initialRoute: .home)
func application(_ application:didFinishLaunchingWithOptions) -> Bool {
window.rootViewController = basicCoordinator.navigationController
return true
}
Implementation:
...
init(coodinator: AnyCoordinator<HomeRoute>) {
self.coordinator = coodinator
}
func onUsersButtonPress() {
self.coordinator.transition(to: .users)
}
...
RxCoordinator supports cases for custom transitions between view controllers. In the switch case
in prepareTransition(coordinator:)
create an Animation
while specifying your custom presentation transition or/and dismissal transition.
...
func prepareTransition(coordinator: AnyCoordinator<HomeRoute>) -> Transition {
switch self {
...
case .users(let string):
let animation = Animation(presentationAnimation: yourAnimation, dismissalAnimation: YourAwesomeDismissalTransitionAnimation())
var vc = UsersViewController.instantiateFromNib()
let viewModel = UsersViewModelImpl(coordinator: coordinator, string: string)
vc.bind(to: viewModel)
return .push(vc, animation: animation)
...
}
In RxCoordinator is possible to create custom coordinators. For example, a custom coordinator can be created to show Home if the user is logged in, otherwise Login.
class AppCoordinator: Coordinator {
typealias CoordinatorRoute = HomeRoute
var context: UIViewController!
var navigationController: UIViewController = UINavigationController()
init() {}
func presented(from presentable: Presentable?) {
if isLoggedIn {
transition(to: .home)
} else {
transition(to: .login)
}
}
}
Check out this repository as an example project using RxCoordinator.
-
Separation of responsibilities by coordinator being the only component knowing anything related to the flow of your application.
-
Reusable Views and ViewModels because they do not contain any navigation logic.
-
Less coupling between components
-
Changeable navigation: Each coordinator is only responsible for one component and makes no assumptions about its parent. It can therefore be placed wherever we want to.
The Coordinator by Soroush Khanlou
-
Actual navigation code is already written and abstracted away.
-
Clear separation of concerns:
- Coordinator: Coordinates routing of a set of routes.
- Route: Describes navigation path.
- Transition: Describe transition type and animation to new view.
-
Reuse coordinators, routers and transitions in different combinations.
-
Full support for custom transitions/animations.
-
Support for embedding child views / container views.
-
Provides observables for presentation / dismissal of views (E.g. block button until presentation is done).
-
Generic BasicCoordinator class suitable for most use cases and therefore less need to write your own coordinators.
-
Still full support for your own coordinator classes conforming to our Coordinator protocol.
-
Generic AnyCoordinator type erasure class encapsulates all types of coordinators supporting the same set of routes. Therefore you can easily replace coordinators.
-
Use of enum for routes gives you autocompletion and type safety to perform only transition to routes supported by the coordinator.
Describes a navigation path. Creates transition and loads view (and corresponding view model) with the help of the coordinator performing the transition.
An object coordinating the transition to a set of routes pointing to views or other coordinators.
Transitions describe the navigation from one view to another view.
- ViewTransition: Supports basic transitions that every view controller supports
- NavigationTransition: Adds navigation controller specific transitions
Describes presentation/dismissal of a view including the type of the transition and the animation used.
- present: present view controller.
- embed: embed view controller to a container view.
- dismiss: dismiss the view controller that was presented modally by the view controller.
- none: does nothing to the view controller (allows you to handle case later without touching the ViewController)
- push: push view controller to navigation stack. (only in
NavigationTransition
) - pop: pop the top view controller from the navigation stack and updates the display. (only in
NavigationTransition
) - popToRoot: pop all the view controllers on the navigation stack except the root view controller and updates the display. (only in
NavigationTransition
)
To integrate RxCoordinator into your Xcode project using CocoaPods, add this to your Podfile
:
pod 'rx-coordinator'
To integrate RxCoordinator into your Xcode project using Carthage, add this to your Cartfile
:
github "quickbirdstudios/RxCoordinator" ~> 0.5
Then run carthage update
.
If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained over at Carthage.
If you prefer not to use any of the dependency managers, you can integrate RxCoordinator into your project manually, by downloading the source code and placing the files on your project directory.
This tiny library is created with β€οΈ by QuickBird Studios.
Open an issue if you need help, if you found a bug, or if you want to discuss a feature request.
Open a PR if you want to make some changes to RxCoordinator.
RxCoordinator is released under an MIT license. See License.md for more information.