ScreenUI - multi-platform, multi-paradigm routing framework [UIKit, AppKit, SwiftUI]

Core

Just like in Storyboard, the framework works in terms of screens. But of course in code.
Screen defines its content view (controller) and context, that required to make content.
During content creation, screen passes context and router to the content.
Because router retains screen instance, we have the opportunity to define in properties some information for content view. For example, screen title, background color, mode and so on, all that we could define in Storyboard.

Declarative syntax

Screens hierarchy and transitions are defined immediately and in one place.
Here come to the rescue Swift features like @resultBuilder.

Example of app route map
static let transitionsMap = AppScreen(
    Delegate(
        ConditionalWindow {
            Launch()
            Welcome(
                login: Present(Navigation(Login())),
                registration: Present(Navigation(Registration()))
            )
            Tabs {
                Navigation(
                    Feed(
                        page: Push(Page()),
                        match: Push(Match()),
                        gameDay: Push(GameDay(match: Push(Match()))),
                        tournament: Push(Tournament(match: Push(Match())))
                    )
                )
                Navigation(
                    Search(
                        filter: AnySearchFilter()
                            .navigation()
                            .configContent({ $0.isToolbarHidden = false })
                            .present(),
                        user: Present(Navigation(Player(team: Push(Team())))),
                        team: Team(player: Player().push())
                            .navigation()
                            .present(),
                        league: Present(Navigation(League())),
                        match: Present(Navigation(Match()))
                    )
                )
                Navigation(
                    Dashboard(
                        edit: Flow(base: UserEdit(editable: true)).present(),
                        entities: .init(
                            user: Push(Player(team: Team().navigation().present())),
                            team: Push(Team(player: Player().navigation().present())),
                            league: Push(League(
                                team: Push(Team()),
                                tournament: Push(Tournament(match: Push(Match())))
                            ))
                        )
                    )
                )
                Navigation(
                    Messages(
                        settings: Present(
                            Settings(
                                account: Push(AccountInfo()),
                                changePassword: Push(ChangePassword())
                            ).navigation()
                        )
                    )
                )
            }
            .configContent({ tabbar in
                tabbar.prepareViewAppearance()
            })
            .with(((), (), (), ()))
        }
    )
)

Transitions are powered by Swift Keypath Expressions

Implementation uses properties that are defined in screen as routes.

let nextScreen: Push<Self, DetailScreen>

Transitions also like other properties can be optional.

router.move(\.nextScreen, from: self, with: nextScreenContext, completion: nil)

By using a type-erased transition we can define different transitions in each other scenario for the same screen.
Deep transitions can build with subscript interface:

///  [Initial screen]    [Conditional screen]    [Tab screen]    [Some next screen in scenario]    [Run chain from root screen content]
///       /                 /                       |             /                                  /
router[root: <%context%>][case: \.2, <%context%>][select: \.1][move: \.nextScreen, <%context%>].move(from: (), completion: nil)

Status

Current library still is in proof-of-concept state, and requires wide usage and usability tests.
Implementation has such disadvantages:

  • key-path expressions require explicit types of screen, which can grow with increasing hierarchy complexity.
  • complex screens (like containers) can delegate calling transition to nested modules (like child view controllers) and it makes development more difficult.

Generally, it would be interesting for me if SwiftUI get something similar to make routing.
Thanks for visit.

Github page

4 Likes