Need help with FlowKit?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

121 Stars 3 Forks MIT License 35 Commits 0 Opened issues


Screenflow management for iOS

Services available


Need anything else?

Contributors list

# 72,971
35 commits

Build Status codecov

Flow Kit

FlowKit iOS/Swift

Define screen flows easily with FlowKit. Elegant syntax, clear separation of concerns and testability makes it a perfect add-on for your current MV* setup.

let tutorialScreen = Flow(with: TutorialViewController()) { vc, lets in
    vc.onContinue = lets.push(loginScreen)
let loginScreen = Flow(with: LoginViewController()) { vc, lets in
    vc.onLogin = lets.push(dashboardScreen)
    vc.onBack = lets.pop()
let dashboardScreen = Flow(with: DashboardViewController()) { vc, lets in
    vc.onBack = lets.pop()
    vc.onLogOut = lets.popToRoot()

This supports following flow ```text

| | | | | |
| TutorialVC | onContinue() | LoginVC | onLogin() | DashboardVC | | | -----------> | | --------> | | |_________| |___| |__________| ```

This is how any of our view controllers may look like: ```swift class DashboardViewController: UIViewController { var onBack: () -> Void = {} var onLogOut: () -> Void = {}

@IBAction func backButtonTapped(button: UIButton) {

@IBAction func logOutButtonTapped(button: UIButton) { onLogOut() }

} ```

Defining a flow

is a wrapper around any
. Through
you can define how your
interacts with other view controllers. Main interactions possible are: *

This approach has a few major advantages, since your

: - doesn't need to know other view controllers in the town #loosely-coupling - focuses on managing it's own view, rather than managing apps navigation #single-responsibility - is easier to test #testability - code becomes more readable, since navigation-related pieces can be put in one place #readability - entrance and exit points of your
are clearly defined #clear-api

There are multiple ways how to initialize the flow:

  1. Without interactions:
   let yourScreen = Flow(with: YourViewController())

// or e.g. with a custom xib let yourScreen = Flow(with: YourViewController(nibName: "YourView", bundle: nil))

Note that thanks to

is instantiated lazily, i.e. only when needed. #swiftmagic

  1. With interactions (short version)

    let yourScreen = Flow(with: YourViewController()) { vc, lets in
       vc.onBack = lets.pop()
       vc.onAbout = lets.present(otherScreen)
  2. With interactions (long version) ``` let yourScreen = Flow { lets in // let's initialize our ViewController from a storyboard let storyboard = UIStoryboard(name: "YourView", bundle: nil) let vc = storyboard.instantiateViewController(withIdentifier: "YourView") as! YourViewController

    vc.onBack = lets.pop() vc.onAbout = lets.present(otherScreen)

    return vc } ```

Passing arguments

So, let's assume we've got a shopping app.

presents our GreatProduct™. If a user decides to purchase it,
is pushed to the screen to guide user through the checkout process. Now, how can
know which product is actually being purchased? Obviously, it should receive that info from
. This is how to do this:
class ItemViewController: UIViewController {
    var item: Item? // = GreatProduct™
    var quantity = 0

var onCheckout: (Item, Int) -> Void?

@IBAction func checkout(button: UIButton) {
    if let item = item, onCheckout = onCheckout {
        onCheckout(item, quantity)        


class CheckoutViewController: UIViewController { func prepare(item: Item, quantity: Int) { print("User wants to purchase (item) × (quantity)") } }

// our flow

let checkoutScreen = Flow(with: CheckoutViewController(nibName: "CheckoutView", bundle: nil))

let itemScreen = Flow(with: ItemViewController()) { vc, lets in

vc.onCheckout = lets.push(checkoutScreen) { $0.prepare }


The trick here is to forward arguments from

function. This is done exactly here
vc.onCheckout = lets.push(checkoutScreen) { $0.prepare }
In plain words we'd say: ```plain vc. onCheckout= lets.push( checkoutScreen){$0.prepare } hey itemViewController, on checkout lets push checkout screen and prepare it
Important thing to note here is that the signature of `onCheckout` has to be identical with the signature of `prepare`, so arguments can be passed successfully.

Grouping Flows

In a usual scenario it's convenient to group your flows in a separate classes. For example you can have a LoginFlow, SignUpFlow, CheckoutFlow etc... If your app is small, it may be enough to have one MainFlow.

```swift class MainFlow { lazy var tutorialScreen: Flow = Flow { [unowned self] lets in let screen = TutorialViewController()

    screen.onContinue = lets.push(self.loginScreen) { $0.prepare }

    return screen

lazy var dashboardScreen: Flow<dashboardviewcontroller> = Flow { [unowned self] lets in
    let screen = DashboardViewController()

    screen.onBack = lets.pop()
    screen.onLogOut = lets.popTo(self.loginScreen)
    screen.onExit = lets.popToRoot()

    return screen

lazy var loginScreen: Flow<loginviewcontroller> = Flow { [unowned self] lets in
    let screen = LoginViewController()

    screen.onLogin = lets.push(self.dashboardScreen)
    screen.onBack = lets.pop()

    return screen


note 1. We had to use

lazy var
to allow
to reference
and vice versa. With regular
compiler wouldn't allow us to use

note 2. Unfortunately due to compiler bug we have declare variable type, otherwise we can't use



under construction

I'd like to create a custom nimble matchers, so testing our flows would be as easy as writing them: ```swift let mainScreen = Flow(with: MainViewController()) mainScreen.letsFactory = LetsSpyFactory() let spy = mainScreen.letsFactory.makeSpy()

let vc = mainScreen.viewController

expect(vc.onBack).to(haveBeenBackedWith(spy.pop)) expect(vc.onLogOut).to(haveBeenBackedWith(spy.popTo(loginScreen))) expect(vc.onExit).to(haveBeenBackedWith(spy.popToRoot())) ```


under construction

FlowKit shall integrate well with:

  • RxSwift (in preparation)
    let tutorialScreen = Flow(with: TutorialViewController()) { vc, lets in
  This is just an illustration, I'm not yet sure how this is going to look like.


This project is created and maintained by Filip Zawada. It was created as a remedy for navigation problems in my last apps.

This projects is the next iteration over the idea of Flow Controllers, described by Krzysztof Zabłocki.


To be chosen soon.

designed in Poland, assembled in Swift 🙃

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.