A simple routing library for iOS projects.
enum Route: RouteType {
case homeTab
case login
case profile(withID: Int)
}
class Router: XRouter<Route> {
/// Configure the destination view controller for the route
override func prepareForTransition(to route: Route) throws -> UIViewController {
switch route {
case .homeTab:
return container.resolve(HomeTabCoordinator.self)?.rootViewController
case .login:
return container.resolve(LoginFlowCoordinator.self)?.startFlow()
case .profile(let id):
return ProfileViewController(withID: id)
}
}
}
// Navigate to a route
router.navigate(to: .loginFlow)
// ... or open a route from a URL
router.openURL(url)
XRouter also supports the RxSwift framework out of the box. Bindings exist for navigate(to:)
, which returns a Completable
, and openURL(_:)
, which returns a Single<Bool>
.
router.rx.navigate(to: .loginFlow) // -> Completable
router.rx.openURL(url) // -> Single<Bool>
XRouter provides support for deep links and universal links.
You only need to do one thing to add URL support for your routes.
Implement the static method registerURLs
:
enum Route: RouteType {
/* ... */
/// Register URLs
static func registerURLs() -> URLMatcherGroup<Route>? {
return .group("store.example.com") {
$0.map("products") { .allProducts }
$0.map("user/*/logout") { .logout }
$0.map("products/{category}/view") {
try .products(category: $0.param("category"))
}
$0.map("user/{id}/profile") {
try .viewProfile(withID: $0.param("id"))
}
}
}
}
Here is an example with multiple hosts:
enum Route: RouteType {
/* ... */
/// Register URLs
static func registerURLs() -> URLMatcherGroup<Route>? {
return .init(matchers: [
.group(["example.com", "store.example.com"]) {
$0.map("products/") { .allProducts }
$0.map("user/*/logout") { .logout }
},
.group("affiliate.website.net.au") {
$0.map("*/referral/") { .openReferral(for: $0.rawURL) }
}
])
}
}
Then you can call the openURL(_:animated:completion:)
and/or continue(_ userActivity:)
methods, e.g. from in your AppDelegate:
extension AppDelegate {
/// Open URL
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return router.openURL(url, animated: false)
}
/// Continue user activity (universal links)
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
return router.continue(userActivity)
}
}
If you handle all navigation errors in the same way, you can override the received(unhandledError:)
method.
class Router: XRouter<Route> {
/* ... */
override func received(unhandledError error: Error) {
log.error("Oh no! An error occured: \(error)")
}
}
Or you can set a custom completion handler for some individual navigation action:
router.navigate(to: .profilePage(withID: 24)) { (optionalError) in
if let error = optionalError {
print("Oh no, we couldn't go here because there was an error!")
}
}
Here is an example using the popular Hero Transitions library.
Set the customTransitionDelegate
for the Router
:
router.customTransitionDelegate = self
(Optional) Define your custom transitions as a static reference
extension RouteTransition {
static var heroCrossFade: RouteTransition {
return .custom(identifier: "HeroCrossFade")
}
}
Implement the delegate method performTransition(...)
:
extension Router: RouterCustomTransitionDelegate {
/// Handle custom transitions
func performTransition(to viewController: UIViewController,
from sourceViewController: UIViewController,
transition: RouteTransition,
animated: Bool,
completion: ((Error?) -> Void)?) {
guard transition == .heroCrossFade else {
assertionFailed("Unhandled custom transition \(transition.name)")
completion?(nil)
return
}
sourceViewController.hero.isEnabled = true
destViewController.hero.isEnabled = true
destViewController.hero.modalAnimationType = .fade
// Creates a container nav stack
let containerNavController = UINavigationController()
containerNavController.hero.isEnabled = true
containerNavController.setViewControllers([newViewController], animated: false)
// Present the hero animation
sourceViewController.present(containerNavController, animated: animated) {
completion?(nil)
}
}
}
And override the transition to your custom in your Router:
override func transition(for route: Route) -> RouteTransition {
switch route {
case .profile:
return .heroCrossFade
default:
return .inferred
}
}
Complete documentation is available here.
To run the example project, clone the repo, and run it in Xcode 10.
XRouter is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'XRouter'
Reece Como, reece@hubr.io
XRouter is available under the MIT license. See the LICENSE file for more info.