ReSwift-Router

by ReSwift

Declarative Routing in Swift, Extension for ReSwift

467 Stars 77 Forks Last release: about 1 year ago (0.7.1) MIT License 133 Commits 17 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Carthage compatible Platform support

A declarative router for ReSwift. Allows developers to declare routes in a similar manner as URLs are used on the web.

Using ReSwiftRouter you can navigate your app by defining the target location in the form of a URL-like sequence of identifiers:

mainStore.dispatch(
    SetRouteAction(["TabBarViewController", StatsViewController.identifier])
)

About ReSwiftRouter

ReSwiftRouter is still under development and the API is neither complete nor stable at this point.

When building apps with ReSwift you should aim to cause all state changes through actions - this includes changes to the navigation state.

This requires to store the current navigation state within the app state and to use actions to trigger changes to that state - both is provided ReSwiftRouter.

Installation

CocoaPods

You can install ReSwiftRouter via CocoaPods by adding it to your

Podfile
:
use_frameworks!

source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0'

pod 'ReSwift' pod 'ReSwiftRouter'

And run

pod install
.

Carthage

You can install ReSwiftRouter via Carthage by adding the following line to your Cartfile:

github "ReSwift/ReSwift-Router"

Configuration

Extend your app state to include the navigation state:

import ReSwiftRouter

struct AppState: StateType { // other application state var navigationState: NavigationState }

After you've initialized your store, create an instance of

Router
, passing in a reference to the store and to the root
Routable
. Additionally you will need to provide a closure that describes how to access the
navigationState
of your application state:
router = Router(store: mainStore, rootRoutable: RootRoutable(routable: rootViewController)) { state in 
    state.select { $0.navigationState }
}

We'll discuss

Routable
in the next main section.

Calling the Navigation Reducer

The

NavigationReducer
is provided as part of
ReSwiftRouter
. You need to call it from within your top-level reducer. Here's a simple example from the specs:
struct AppReducer: Reducer {
    func handleAction(action: Action, state: FakeAppState?) -> FakeAppState {
        return FakeAppState(
            navigationState: NavigationReducer.handleAction(action, state: state?.navigationState)
        )
    }
}

This will make reducer handle all routing relevant actions.

Implementing
Routable

ReSwiftRouter works with routes that are defined, similar to URLs, as a sequence of elements e.g.

["Home", "User", "UserDetail"]
.

ReSwiftRouter is agnostic of the UI framework you are using - it uses

Routable
s to implement that interaction.

Each route element is mapped to one responsible

Routable
. The
Routable
needs to be able to present a child, hide a child or replace a child with another child.

Here is the

Routable
protocol with the methods you should implement:
public protocol Routable {

func push(
    _ element: RouteElement,
    animated: Bool,
    completion: @escaping RoutingCompletion) -> Routable

func pop(
    _ element: RouteElement,
    animated: Bool,
    completion: @escaping RoutingCompletion)

func change(
    _ from: RouteElement,
    to: RouteElement,
    animated: Bool,
    completion: @escaping RoutingCompletion) -> Routable

}

As part of initializing

Router
you need to pass the first
Routable
as an argument. That root
Routable
will be responsible for the first route element.

If e.g. you set the route of your application to

["Home"]
, your root
Routable
will be asked to present the view that corresponds to the element
"Home"
.

When working on iOS with UIKit this would mean the

Routable
would need to set the
rootViewController
of the application.

Whenever a

Routable
presents a new route element, it needs to return a new
Routable
that will be responsible for managing the presented element. If you want to navigate from
["Home"]
to
["Home", "Users"]
the
Routable
responsible for the
"Home"
element will be asked to present the
"User"
element.

If your navigation stack uses a modal presentation for this transition, the implementation of

Routable
for the
"Home"
element might look like this:
func push(_ element: RouteElement, animated: Bool, completion: @escaping RoutingCompletion) -> Routable {

if element == "User" {
    // 1.) Perform the transition
    userViewController = UIStoryboard(name: "Main", bundle: nil)
        .instantiateViewControllerWithIdentifier("UserViewController") as! Routable

    // 2.) Call the `completion` once the transition is complete
    presentViewController(userViewController, animated: false,
        completion: completion)

    // 3.) Return the Routable for the presented element. For convenience
    // this will often be the UIViewController itself. 
    return userViewController
}

// ...

}

func pop(_ element: RouteElement, animated: Bool, completion: @escaping RoutingCompletion)

if element == "Home" {
    dismissViewControllerAnimated(false, completion: completion)
}

// ...

}

Calling the Completion Handler within Routables

ReSwiftRouter needs to throttle the navigation actions, since many UI frameworks including UIKit don't allow to perform multiple navigation steps in parallel. Therefor every method of

Routable
receives a
completion
handler. The router will not perform any further navigation actions until the completion handler is called.

Changing the Current Route

Currently the only way to change the current application route is by using the

SetRouteAction
and providing an absolute route. Here's a brief example:
@IBAction func cancelButtonTapped(sender: UIButton) {
    mainStore.dispatch(
        SetRouteAction(["TabBarViewController", StatsViewController.identifier])
    )
}

As development continues, support for changing individual route elements will be added.

Contributing

Compiling & Running tests

ReSwiftRouter uses Carthage for its development dependencies. To build or test any of the targets, run

carthage bootstrap
.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.