magnuskahr

writing code

Announcing FlowNavigation

Today, I am happy to announce my new SwiftUI framework: FlowNavigation.

Backstory

I'm working on a new app (hopefully one that actually ships), and I ran into a problem I've seen before—how do you define a series of views a user needs to go through in a clean and manageable way?

Imagine that you have a sign-up flow. You would probably need your user to go through several screens, enter a bunch of information, and submit it somewhere. You might also need information from Screen A to be used on Screen B. One could solve this with a coordinator, but I argue there are better ways for handling linear flows.

What I envision is simply listing a series of screens, defining how their outputs should be handled, and letting that list dictate the flow itself.

Disclaimer: I have the idea for this system (linear flows) from the Lunar-app, which I worked on, but all code, the final functionality and implementation details are my own.

Defining a Flow

Creating a flow using FlowNavigation is easy - it is just a view!

Flow {
    EmailScreen()
    PasswordScreen()
    
    // Read the output of each screen and pass it along to the submission screen
    FlowReader { proxy in 
        SubmitScreen(
            email: try proxy.data(for: EmailScreen.self)
            password: try proxy.data(for: PasswordScreen.self)
        )
    }
}

This creates a linear flow that first presents the email screen, then the password screen, and finally reads both email and password before passing them along to the submission screen. Under the hood, Flow uses NavigationStack and simply ensures that the next screen is pushed when requested.

A screen is defined using the FlowScreen protocol:

struct EmailScreen: FlowScreen {
    // A screenId is used to organize output data of screens
    static let screenId = "Email"
    
    // FlowScreens can fully use any SwiftUI propperty wrapper like @State
    @State private var email = ""
    
    // The control is generic over the output type if this screen. 
    // For simplicity we store the email as String
    func body(control: FlowScreenControl<String>) -> some View {
        VStack {
            // ... textfield etc. omitted
            
            Button("Continue") {
                Task {
                    // Pass along the email as output of this screen
                    await control.next(email)
                }
            } 
        }
        
    }
}

I believe this approach makes it easy to construct linear flows in SwiftUI. FlowNavigation is still in beta and has not yet been battle-tested, but I am excited to share this concept and let people explore it. Make sure to check out the documentation, as it explains many features in detail and more to come. I’d love to hear any feedback if you decide to try the framework!