Every navigation operation is highly customizable. Define your configuration by chaining configuration methods in configuration block.
Configuration options:
- Animating
- Observing completion
- Observing success
- Observing failure
- Embedding
- Passing data
- Caching
- Protection
- State restoration
- Specifying origin view controller
More details about options are available on the Documentation page.
| Method | Arguments |
|---|---|
animated(_:) |
Bool |
By passing false, transition will perform without animation. Default:
true
| Method | Arguments |
|---|---|
transitioningDelegate(_:) |
UIViewControllerTransitioningDelegate |
Pass the delegate object that provides transition animator, interactive controller, and custom presentation controller objects.
| Method | Arguments |
|---|---|
completion(_:) |
() -> Void |
The block to execute after the navigation finishes (after the
viewDidAppear(_:)method is called on the presented view controller).
| Method | Arguments |
|---|---|
onSuccess(_:) |
(Result) -> Void |
The block to execute before the navigation starts (after the view controller is instantiated).
| Method | Arguments |
|---|---|
onFailure(_:) |
(Error) -> Void |
The block to execute on navigation failure.
| Method | Arguments |
|---|---|
embedded(in:) |
EmbeddingType |
Embeds view controller in another view controller. Pass
EmbeddingTypeenum to specify behavior.
| Method | Arguments |
|---|---|
embedded(in:) |
EmbeddingProtocol.Type |
Embeds view controller in another view controller. Pass type conforming
EmbeddingProtocol.
| Method | Arguments |
|---|---|
embeddedInNavigationController() |
None |
Embeds view controller in
UINavigationController.
| Method | Arguments |
|---|---|
passData(_:) |
Any |
withData(_:) |
Any |
passData(_:) |
T |
withData(_:) |
T |
passDataInBlock(_:) |
((T) -> Void) -> Void |
withDataInBlock(_:) |
((T) -> Void) -> Void |
TODO: describe usage
| Method | Arguments |
|---|---|
keepAlive(within:cacheIdentifier:) |
Lifetime, String |
Pass an object conforming
Lifetimeprotocol.
Implement following method:
func die(_ kill: @escaping () -> Void)
Call kill() to invalidate cache.
Let's say following use case has to be implemented:
- When some action
A1occurs (eg. button is tapped),VC1is presented
- If
VC1is dismissed andA1action is repeated within60seconds, presentedVC1should be the same instance fromstep 1of this use case- Otherwise, new instance of
VC1should be presented
This can be easily achieved by passing an object conforming Lifetime protocol.
Let's make an object that is representing some time duration, in this case - seconds. It has just one property:
let seconds: TimeInterval
class Age: Lifetime {
let seconds: TimeInterval
init(seconds: TimeInterval) {
self.seconds = seconds
}
func die(_ kill: @escaping () -> Void) {
print("will invalidate cache in \(seconds) second(s)")
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
kill()
print("Cache invalidated")
}
}
}Applying Lifetime to navigation:
Navigate.present { $0
.to(VC1.self)
.keepAlive(within: Age(seconds: 60), identifier: "CACHE_IDENTIFIER")
}| Method | Arguments |
|---|---|
protect(with:) |
ProtectionSpace |
Pass an object conforming
ProtectionSpaceprotocol.
Implement following method:
func shouldProtect(unprotect: @escaping () -> Void, failure: @escaping (Error) -> Void) -> Bool
Call unprotect() when you are ready to execute navigation.
- Unauthenticated user initiates navigation to
VC1that is available only to authenticated users- When such navigation is requested,
VC1should not open immediately- Authentication view controller
VC2is presented- When user is finally authenticated,
VC2is dismissed andVC1is presented automatically without need to initiate navigation again
How to achieve this?
-
Declare protection space:
class Auth: ProtectionSpace { let authenticationService = SomeAuthService() // MARK: ProtectionSpace func shouldProtect(unprotect: @escaping () -> Void, failure: @escaping (Error) -> Void) -> Bool { guard let shouldProtect = !authenticationService.isUserSignedIn else { // should not protect if user is signed in return false } // present some AuthVC Navigate.present { $0 .to(AuthVC.self) .onSuccess({ (result) in authenticationService.userDidSignIn { // `authenticationService.isUserSignedIn` now resolves to `true` // dismiss AuthVC result.toViewController?.dismiss(animated: true, completion: { // unprotect and proceed with navigation unprotect() }) } authenticationService.userDidFailToSignIn { error in // pass error if want to trigger onFailure blocks // on main navigation request failure(error) } }) } return shouldProtect } }
-
Apply
ProtectionSpaceinstance to navigation:Navigate.present { (navigate) in navigate .to(VC1.self) .protect(with: Auth()) }
CoreNavigation interacts with iOS state restoration engine and provides solution to cases where some checks has to be done before state restoration should continue. However, there are some prerequisites that have to be met:
-
If you do not use storyboards and instead create your window and root view controller in code, make sure you create them in AppDelegate's
application:willFinishLaunchingWithOptions:instead ofapplication:didFinishLaunchingWithOptions:From Apple's documentation:
Important
If your app relies on the state restoration machinery to restore its view controllers, always show your app’s window from this method. Do not show the window in your app’s application(:didFinishLaunchingWithOptions:) method. Calling the window’s makeKeyAndVisible() method does not make the window visible right away anyway. UIKit waits until your app’s application(:didFinishLaunchingWithOptions:) method finishes before making the window visible on the screen.
-
Conform your App delegate to
StateRestorationDelegateand implement:application:shouldSaveApplicationState:application:shouldRestoreApplicationState:application:stateRestorationBehaviorForContext:
Example:
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, StateRestorationDelegate { var window: UIWindow? = UIWindow() lazy var rootViewController = ViewController() func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { rootViewController?.restorationIdentifier = "root" window?.restorationIdentifier = "main_window" window?.rootViewController = rootViewController window?.makeKeyAndVisible() return true } func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool { return true } func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool { return true } func application(_ application: UIApplication, stateRestorationBehaviorForContext context: StateRestorationContext) -> StateRestorationBehavior { // check if restoration is heading to some protected view controller if context.identifier == "my-account" { return .protect(protectionSpace: Auth(), onUnprotect: nil, onFailure: nil) } context.onUnprotect { (viewController: UIViewController) in // if you unprotect state restoration on different thread, you can manually present resolved view controller from here } return .allow // or .reject if state restoration is not wanted } func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [Any], coder: NSCoder) -> UIViewController? { /* Fallback to default iOS state restoration handling. Only implement this method if you handle state restoration manually. */ return nil } }
| Method | Arguments |
|---|---|
stateRestorable() |
None |
Prepares view controller for state restoration.
If you use this method, then your
AppDelegatemust conformStateRestorationDelegateprotocol.
| Method | Arguments |
|---|---|
stateRestorable(identifier:) |
String |
Prepares view controller for state restoration with ability to pass custom restoration identifier.
If you use this method, then your
AppDelegatemust conformStateRestorationDelegateprotocol.
| Method | Arguments |
|---|---|
stateRestorable(identifier:class:) |
String, UIViewControllerRestoration.Type |
Prepares view controller for state restoration with ability to pass custom restoration identifier and class.
StateRestorationDelegate protocol
Implement following method:
-
func application(_ application: UIApplication, stateRestorationBehaviorForContext context: StateRestorationContext) -> StateRestorationBehaviorWhen state restoration is handled through
CoreNavigation, on app launchAppDelegatewill be asked to provide behavior for single state restoration case.Method must return a case from
StateRestorationBehaviorenum.Method will receive
StateRestorationContextargument (used for meta purposes) which has following properties:Property name Type Description restorationIdentifierStringState restoration identifier applied to view controller. viewControllerClassUIViewController.TypeA view controller class that is going to be restored. protectionSpaceClassAnyClass?A class of object passed in protect(with:)method.dataAny?Data passed in passData(_:)method.
Note
View controllers restored by CoreNavigation state restoration engine & conforming DataReceivable protocol will receive data passed during navigation so there is no need to rely on decodeRestorableState(with:) and encodeRestorableState(with:). To support this feature, passed data must conform to NSCoding protocol.
| Method | Arguments |
|---|---|
from(_:) |
UIViewController |
CoreNavigationautomatically determines visible view controller which is used to navigate from. Calling this method will override this behavior.