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
EmbeddingType
enum 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
Lifetime
protocol.
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
A1
occurs (eg. button is tapped),VC1
is presented
- If
VC1
is dismissed andA1
action is repeated within60
seconds, presentedVC1
should be the same instance fromstep 1
of this use case- Otherwise, new instance of
VC1
should 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
ProtectionSpace
protocol.
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
VC1
that is available only to authenticated users- When such navigation is requested,
VC1
should not open immediately- Authentication view controller
VC2
is presented- When user is finally authenticated,
VC2
is dismissed andVC1
is 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
ProtectionSpace
instance 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
StateRestorationDelegate
and 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
AppDelegate
must conformStateRestorationDelegate
protocol.
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
AppDelegate
must conformStateRestorationDelegate
protocol.
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) -> StateRestorationBehavior
When state restoration is handled through
CoreNavigation
, on app launchAppDelegate
will be asked to provide behavior for single state restoration case.Method must return a case from
StateRestorationBehavior
enum.Method will receive
StateRestorationContext
argument (used for meta purposes) which has following properties:Property name Type Description restorationIdentifier
String
State restoration identifier applied to view controller. viewControllerClass
UIViewController.Type
A view controller class that is going to be restored. protectionSpaceClass
AnyClass?
A class of object passed in protect(with:)
method.data
Any?
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 |
CoreNavigation
automatically determines visible view controller which is used to navigate from. Calling this method will override this behavior.