Swift based Application State Router
Featherweight Router is a declarative routing handler that decouples ViewControllers from each other. It follows a Coordinator and Presenter pattern, also referred to as Flow Controllers.
Featherweight Router makes an excellent MVVM companion and fits right in with Redux style State Flow and Reactive frameworks.
The Coordinator is constructed by declaring a route hierarchy mapped with a URL structure.
By using mapping UI to URLs, it makes it easy to add automatic URL scheme handling in the future.
As the State changes over time, so will the UI projection of that State.
Given any State value the UI must be predictable and repeatable.
Displaying the same State on a phone and tablet for example, can result in different UIs. The device dependent state should remain on that device. An OS X and iOS app can use the same State and logic classes and interchange Routers for representing the State.
If the UI is a projection of State, then the current Path should be included in that State too.
The user tapping a back button is easy to capture and generate and action that updates the State Path which causes the UI change. But a user 'swiping' back a view is harder to capture. It should instead generate an action on completion to update the State Path. Then, if the current UI already matches the new State no UI changes are necessary.
Each view component should be testable and predictable. If any component that makes up the UI is not predictable, then neither is the UI.
Although the UI should be a projection of State + Path only the Path should be passed to the Router when State changes. Using the Router for State propagation ties the Router to the State. Following the single responsibility principle, the Router only needs updates when the Path changes and callbacks to notify State of Path changes caused by the user outside of the standard Routes (ie, user swiped back in a navigation controller).
import FeatherweightRouter
func appRouter(store: AppStore) -> Router<UIViewController, String> {
return Router(tabBarPresenter()).junction([
Router(navigationPresenter(title: "Animals")).stack([
Router(animalListPresenter(store)).route(predicate: {$0 == "animals"}, children: [
Router(animalPresenter(store)).route(predicate: {$0.matches("(?<id>\\w+)")}),
]),
]),
Router(navigationPresenter(title: "Zoos")).stack([
Router(zooListPresenter(store)).route(predicate: {$0 == "zoos"}, children: [
Router(zooPresenter(store)).route(predicate: {$0.matches("(?<id>\\w+)")}),
]),
]),
])
}
func appCoordinator() -> UIViewController {
let store = createStore(reducer: appReducer, initialState: nil)
let router = appRouter(store: store)
store.state.map { $0.route }.subscribe(next: router.setRoute)
return router.presenter
}
- Path / URL: A String representing the current view or UI state the application is in. This can include hierarchy and view dependent information as parameters or query values. Ie, viewing a user profile
- State: A stream of values over time.
- UI: User Interface: A visual representation of the State that a user can interact with.
All content is licensed under the Apache-2.0 unless otherwise stated.
Copyright is owned by Karl Bowden and Featherweight Labs unless otherwise stated.