TurboNavigationController
A drop-in controller for Turbo Native apps to handle common navigation flows.
Note: This package is still being actively developed and subject to breaking changes without warning.
Why use this?
Turbo Native apps require a fair amount of navigation handling to create a decent experience.
Unfortunately, not much of this is built into turbo-ios. A lot of boilerplate is required to have anything more than basic pushing/popping of screens.
This package abstracts that boilerplate into a single navigation controller. You can drop it into your app and not worry about handling every flow manually.
I’ve been using something a version of this on the dozens of Turbo Native apps I’ve built over the years.
Handled flows
When a link is tapped, turbo-ios sends a VisitProposal
to your application code. Based on the Path Configuration, different PathProperties
will be set.
- Current context – What state the app is in.
modal
– a modal is currently presenteddefault
– otherwise
- Given context – Value of
context
on the requested link.modal
ordefault
/blank
- Given presentation – Value of
presentation
on the proposal.replace
,pop
,refresh
,clear_all
,replace_root
,none
,default
/blank
- Navigation – The behavior that the navigation controller provides.
Current Context | Given Context | Given Presentation | New Presentation |
---|---|---|---|
default |
default |
default |
Push on main stack (or) Replace if visiting same page (or) Pop (and visit) if previous controller is same URL |
default |
default |
replace |
Replace controller on main stack |
default |
modal |
default |
Present a modal with only this controller |
default |
modal |
replace |
Present a modal with only this controller |
modal |
default |
default |
Dismiss then Push on main stack |
modal |
default |
replace |
Dismiss then Replace on main stack |
modal |
modal |
default |
Push on the modal stack |
modal |
modal |
replace |
Replace controller on modal stack |
default |
(any) | pop |
Pop controller off main stack |
default |
(any) | refresh |
Pop on main stack then |
modal |
(any) | pop |
Pop controller off modal stack (or) Dismiss if one modal controller |
modal |
(any) | refresh |
Pop controller off modal stack then Refresh last controller on modal stack (or) Dismiss if one modal controller then Refresh last controller on main stack |
(any) | (any) | clearAll |
Dismiss if modal controller then Pop to root then Refresh root controller on main stack |
(any) | (any) | replaceRoot |
Dismiss if modal controller then Pop to root then Replace root controller on main stack |
(any) | (any) | none |
Nothing |
Examples
To present forms (URLs ending in /new
or /edit
) as a modal, add the following to the rules
key of your Path Configuration.
{
"patterns": [
"/new$",
"/edi$"
],
"properties": {
"context": "modal"
}
}
To hook into the “refresh” turbo-rails native route, add the following to the rules
key of your Path Configuration. You can then call refresh_or_redirect_to
in your controller to handle Turbo Native and web-based navigation.
{
"patterns": [
"/refresh_historical_location"
],
"properties": {
"prsentation": "refresh"
}
}
Getting started
Check out the Demo app for an example on how to use TurboNavigationController
.
More detailed instructions are coming soon. PRs are welcome!
Demo project
The Demo/
directory includes an iOS app and Ruby on Rails server to demo the package.
It shows off most of the navigation flows outlined above. There is also an example CRUD resource for more real world applications of each.
Custom overrides
You can also pass an optional delegate to TurboNavigationController
to handle custom routing.
This is useful to break out of the default behavior and/or render a native screen.
class SceneDelegate {
let turboNavigationController = TurboNavigationController(session: session, modalSession, modalSession, delegate: self)
// ...
}
extension SceneDelegate: TurboNavigationDelegate {
func shouldRoute(_ proposal: VisitProposal) -> Bool {
if proposal.url.path.last == "sign_in" {
// TurboNavigationController stop processing the request.
// Do something entirely custom with this link click.
return false
}
return true
}
func customController(for proposal: VisitProposal) -> UIViewController? {
if proposal.url.path.last == "numbers" {
// Let TurboNavigationController route this custom controller.
return NumbersViewController()
}
// Default behavior - TurboNavigationController routes a VisitableViewController.
return nil
}
}