You need get a router subclass for your destination module to perform route. You can use protocol to get router class.
ZIKRouter is written in Objective-C. If your project is in Swift, use ZRouter
.
It's safe to perform route with routable protocol.
class TestViewController: UIViewController {
func showEditorViewController() {
Router.perform(
to: RoutableView<EditorViewInput>(),
path: .push(from: self),
configuring: { (config, _) in
// Config the route
config.successHandler = { destination in
// Transition succeed
}
config.errorHandler = { (action, error) in
// Transition failed
}
// Config the destination before performing route
config.prepareDestination = { destination in
// destination is inferred as EditorViewInput
// Config editor view
destination.delegate = self
destination.constructForCreatingNewNote()
}
})
}
}
When a route is for showing views from a limited list, you can use Switchable
.
enum RequestError: Error {
case invalidAccount
case networkNotConnected
}
class TestViewController: UIViewController {
func showViewForError(_ error: RequestError) {
var switchableView: SwitchableView
switch error {
case .invalidAccount:
switchableView = SwitchableView(RoutableView<LoginViewInput>())
case .networkNotConnected:
switchableView = SwitchableView(RoutableView<NetworkDisconnectedViewInput>())
}
Router.to(switchableView)?.perform(path: .push(from: self))
}
}
@implementation TestViewController
- (void)showEditorViewController {
[ZIKRouterToView(EditorViewInput)
performPath:ZIKViewRoutePath.presentModallyFrom(self)
configuring:^(ZIKViewRouteConfiguration *config) {
config.animated = YES;
config.prepareDestination = ^(id<EditorViewInput> destination) {
destination.delegate = self;
[destination constructForCreatingNewNote];
};
config.succeeHandler = ^(id<EditorViewInput> destination) {
// Transition completed
};
config.errorHandler = ^(ZIKRouteAction routeAction, NSError *error) {
// Transition failed
};
}];
}
Comparing to performPath:configuring:
method, performPath:strictConfiguring:
method can give much strict compiler checking:
@implementation TestViewController
- (void)showEditorViewController {
[ZIKRouterToView(EditorViewInput)
performPath:ZIKViewRoutePath.presentModallyFrom(self)
strictConfiguring:^(ZIKViewRouteStrictConfiguration<id<EditorViewInput>> *config,
ZIKViewRouteConfiguration *module) {
config.animated = YES;
// Type of prepareDestination block changes with the router's generic parameters.
config.prepareDestination = ^(id<EditorViewInput> destination){
destination.delegate = self;
[destination constructForCreatingNewNote];
};
config.successHandler = ^(id<EditorViewInput> destination) {
// Transition completed
};
}];
}
Type of ZIKViewRouteStrictConfiguration
changes with it's generic parameter. So there will be compile checking when you configuring the configuration.
But there is bug in Xcode auto completions. Parameters in ZIKViewRouteStrictConfiguration
's methods are not correctly completed, you have to manually fix the type.
If you only want to get the destination, use makeDestination
, and prepare the destination in block.
Swift Sample:
/// time service's interface
protocol TimeServiceInput {
func currentTimeString() -> String
}
class TestViewController: UIViewController {
@IBOutlet weak var timeLabel: UILabel!
func callTimeService() {
// Get service for TimeServiceInput
let timeService = Router.makeDestination(
to: RoutableService<TimeServiceInput>(),
preparation: { destination in
// Prepare the service
})
// Call service
timeLabel.text = timeService.currentTimeString()
}
}
Objective-C Sample
/// time service's interface
@protocol TimeServiceInput <ZIKServiceRoutable>
- (NSString *)currentTimeString;
@end
@interface TestViewController ()
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@end
@implementation TestViewController
- (void)callTimeService {
// Get service for TimeServiceInput
id<TimeServiceInput> timeService = [ZIKRouterToService(TimeServiceInput) makeDestination];
// Call service
self.timeLabel.text = [timeService currentTimeString];
}
Some views have custom transition animation or some special transition, like switching tab bar item. You can do custom transition in your router.
Steps to support custom transition:
- Override
supportedRouteTypes
, addZIKViewRouteTypeCustom
- If the router needs to validate the configuration, override
-validateCustomRouteConfiguration:removeConfiguration:
- Override
canPerformCustomRoute
to check whether the router can perform route now because the default return value is false - Override
performCustomRouteOnDestination:fromSource:configuration:
to do custom transition. If the transition is performing a segue, use_performSegueWithIdentifier:fromSource:sender:
- Manage router's state with
beginPerformRoute
,endPerformRouteWithSuccess
,endPerformRouteWithError:
If you want to do custom route action:
- Override
-performRouteOnDestination:configuration:
- Before performing custom action, call
prepareDestinationForPerforming
to prepare the destination - After performing custom action, call
endPerformRouteWithSuccess
orendPerformRouteWithError
to change router's state
Most service routers are just for getting a service object. You can use Make Destination.
After performing, you will get a router instance. You can hold the router and remove route later. See Remove Route.
If you get a destination from other place, you can perform on the destination with its router.
For example, an UIViewController supports 3D touch, and implments UIViewControllerPreviewingDelegate
:
class SourceViewController: UIViewController, UIViewControllerPreviewingDelegate {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
// Return the destination UIViewController to let system preview it
let destination = Router.makeDestination(to: RoutableView<DestinationViewInput>())
return destination
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
guard let destination = viewControllerToCommit as? DestinationViewInput else {
return
}
// Show the destination
Router.to(RoutableView<DestinationViewInput>())?.perform(onDestination: destination, path: .presentModally(from: self))
}
Objective-C Sample
@implementation SourceViewController
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
// Return the destination UIViewController to let system preview it
UIViewController<DestinationViewInput> *destination = [ZIKRouterToView(DestinationViewInput) makeDestination];
return destination;
}
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
// Show the destination
UIViewController<DestinationViewInput> *destination;
if ([viewControllerToCommit conformsToProtocol:@protocol(DestinationViewInput)]) {
destination = viewControllerToCommit;
} else {
return;
}
[ZIKRouterToView(DestinationViewInput) performOnDestination:destination path:ZIKViewRoutePath.presentModallyFrom(self)];
}
@end
If you don't want to show the destination, but just want to prepare an existing destination, you can prepare the destination with its router.
If the router injects dependencies inside it, this can properly setting the destination instance.
var destination: DestinationViewInput = ...
Router.to(RoutableView<DestinationViewInput>())?.prepare(destination: destination, configuring: { (config, _) in
config.prepareDestination = { destination in
// Prepare
}
})
Objective-C Sample
UIViewController<DestinationViewInput> *destination = ...
[ZIKRouterToView(DestinationViewInput) prepareDestination:destination configuring:^(ZIKViewRouteConfiguration *config) {
config.prepareDestination = ^(id<DestinationViewInput> destination) {
// Prepare
};
}];
If your app needs to support URL scheme, you can use URL Router.
ZIKAnyViewRouter.performURL("app://editor/test_note", path: .push(from: self))
Objective-C Sample
[ZIKAnyViewRouter performURL:@"app://editor/test_note" path:ZIKViewRoutePath.pushFrom(self)];
And handle URL Scheme:
// openURL inside your app or from other app
func openURL(_ url: NSURL) {
// app://editor/test_note
UIApplication.shared.openURL(url)
}
public func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlString = url.absoluteString
if let _ = ZIKAnyViewRouter.performURL(urlString, fromSource: self.rootViewController) {
return true
} else if let _ = ZIKAnyServiceRouter.performURL(urlString) {
return true
} else {
return false
}
}
Objective-C Sample
// openURL inside your app or from other app
- (void)openURL:(NSURL *)url {
// app://editor/test_note
[[UIApplication sharedApplication] openURL: url];
}
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([ZIKAnyViewRouter performURL:urlString fromSource:self.rootViewController]) {
return YES;
} else if ([ZIKAnyServiceRouter performURL:urlString]) {
return YES;
} else {
return NO;
}
}
If you want to send custom event to module, you can enumerate all router classes and perform event:
func applicationDidEnterBackground(_ application: UIApplication) {
// Send custom event
Router.enumerateAllViewRouters { (routerType) in
if routerType.responds(to: #selector(applicationDidEnterBackground(_:))) {
routerType.perform(#selector(applicationDidEnterBackground(_:)), with: application)
}
}
Router.enumerateAllServiceRouters { (routerType) in
if routerType.responds(to: #selector(applicationDidEnterBackground(_:))) {
routerType.perform(#selector(applicationDidEnterBackground(_:)), with: application)
}
}
}
Objective-C Sample
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Send custom event
[ZIKAnyViewRouter enumerateAllViewRouters:^(Class _Nonnull __unsafe_unretained routerClass) {
if ([routerClass respondsToSelector:@selector(applicationDidEnterBackground:)]) {
[routerClass applicationDidEnterBackground:application];
}
}];
[ZIKAnyServiceRouter enumerateAllServiceRouters:^(Class _Nonnull __unsafe_unretained routerClass) {
if ([routerClass respondsToSelector:@selector(applicationDidEnterBackground:)]) {
[routerClass applicationDidEnterBackground:application];
}
}];
}
If the module want to handle event, just implements class method in router:
class EditorViewRouter: ZIKViewRouter<EditorViewController, ViewRouteConfig> {
...
@objc class func applicationDidEnterBackground(_ application: UIApplication) {
// Handle custom event
}
Objective-C Sample
@implementation EditorViewRouter
...
+ (void)applicationDidEnterBackground:(UIApplication *)application {
// Handle custom event
}
@end