A multi-platform, multi-paradigm routing framework.

Overview

📲 ScreenUI

A multi-platform, multi-paradigm declarative routing framework for iOS/macOS and others, the replacement of Storyboard.

Supports UIKit, AppKit, SwiftUI.

Real world example

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(((), (), (), ()))
        }
    )
)

Table of contents

Main features

  • Complete view of your app’s flow in one place
  • Isolating of screen transitions
  • Deep transitions (deep-link)
  • Abstracted implementation of the transition and its unification
  • Screen constants
  • Cross-platform core
  • Fully strong-typed code

With ScreenUI you will forget about such methods, like func pushViewController(_:), func present(_:), about implementations are based on enums and reducers.

The best achievement of this framework is a combination of strictness (strong types, declarative style, transitions isolation) and flexibility (configurability of screens for each scenario, interchangeability of transitions, optional transitions).

Quick course

Just like in Storyboard, the framework works in terms of screens.

struct SomeScreen: ContentScreen {
    typealias NestedScreen = Self
    typealias Context = Void
    typealias Content = SomeView
    
    /// define constants (color, title, and so on)
    /// define transitions

    func makeContent(_ context: Context, router: Router<NestedScreen>) -> ContentResult<Self> {
        /// initialize a content view
        /// pass `context` and `router` to content (constants will be available through `router`)
        /// return the result
    }
}

Typical screen content implementation:

class SomeView {
    let router: Router<SomeScreen>
    let context: SomeScreen.Context
    let title: String

    init(router: Router<SomeScreen>, context: SomeScreen.Context) {
        self.router = router
        self.context = context
        self.title = router[next: \.title]
    }

    func didLoad() {
        let textLabel = TextLabel(title: context.text)
        ...
    }

    func closeScreen() {
        router.back(completion: nil)
    }
    
    func moveNext() {
        let nextScreenContext = ...
        router.move(\.nextScreen, from: self, with: nextScreenContext, completion: nil)
    }
}

All you need in the next step is to build a screen tree and show any screen from hierarchy:

transitionsMap.router[root: .default][case: \.0, ()].move(from: (), completion: nil)

Due to the specific interface of SwiftUI some things have small changes in API.

SwiftUI example
struct DetailView: View {
    let router: Router<DetailScreen>
    let context: String

    var body: some View {
        VStack {
            Text(context)
            /// optional transition
            if let view = router.move(
                \.nextScreen,
                context: "Subdetail text!!1",
                action: Text("Next"),
                completion: nil
            ) {
                view
            }
            /// move back
            Button("Back") { router.back() }
        }
        .navigationTitle(router[next: \.title])
    }
}

Deep dive

Screen

Every screen must implement the protocol below:

public typealias ContentResult<S> = (contentWrapper: S.Content, screenContent: S.NestedScreen.Content) where S: Screen
public protocol Screen: PathProvider where PathFrom == NestedScreen.PathFrom {
    /// Routing target
    associatedtype NestedScreen: ContentScreen where NestedScreen.NestedScreen == NestedScreen
    /// *UIViewController* subclass in UIKit, *NSViewController* subclass in AppKit, *View* in SwiftUI, or your custom screen representer
    associatedtype Content
    /// Required data that is passed to content
    associatedtype Context = Void
    func makeContent(_ context: Context, router: Router<NestedScreen>) -> ContentResult<Self>
}

Screens that are responsible for performing transitions must implement the protocol ContentScreen.

Screen containers (like Navigation) must implement the protocol ScreenContainer where ScreenContainer.NestedScreen is a transition target.

public struct Navigation<Root>: ScreenContainer where Root: Screen, Root.Content: UIViewController {
    public typealias Context = Root.Context
    public typealias Content = UINavigationController
    public typealias NestedScreen = Root.NestedScreen
    let _root: Root

    public func makeContent(_ context: Root.Context, router: Router<Root.NestedScreen>) -> ContentResult<Self> {
        let (content1, content0) = _root.makeContent(context, router: router)
        let content2 = UINavigationController(rootViewController: content1)
        return (content2, content0)
    }
}

Read more about screens.

Transition

Any transition must implement the protocol below:

public typealias TransitionResult<From, To> = (state: TransitionState<From, To>, screenContent: To.NestedScreen.Content) where From: Screen, To: Screen
public protocol Transition: PathProvider where PathFrom == To.PathFrom {
    associatedtype From: Screen
    associatedtype To: Screen
    associatedtype Context

    func move(from screen: From.Content, state: ScreenState<From.NestedScreen>, with context: Context, completion: (() -> Void)?) -> TransitionResult<From, To>
}

Transitions between the content screens must implement ScreenTransition protocol. Every such transition should provide a back behavior by assign ScreenState.back property.

public struct Present<From, To>: ScreenTransition {
    /// ...
    public func move(from content: From.Content, state: ScreenState<From.NestedScreen>, with context: Too.Context, completion: (() -> Void)?) -> TransitionResult<From, To> {
        let nextState = TransitionState<From, To>()
        nextState.back = .some(Dismiss(animated: animated))
        let (content1, content0) = to.makeContent(context, router: Router(from: to, state: nextState))
        surface.present(content1, animated: animated, completion: completion)
        return (nextState, (content1, content0))
    }
}

To make your screens more flexible, you can define type-erased transitions:

  • AnyScreenTransition - supports transitions where Context is equal to context of the target content screen.
  • PreciseTransition - supports transitions where Context is equal to context of the target container screen.

So, when you will building screen tree, you can set up in one scenario one transition, another transition in the another scenario for the same screen.

Read more about transitions.

Screen path

Router provides a subscript interface to build the path to the screen using Swift Key-path expressions:

///  [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)

/// or using dot syntax
router.root(<%context%>).case(<%context%>).select(\.1).move(\.nextScreen, <%context%>).move(from: (), completion: nil)

You can omit the context value if you sure that screen is presented in hierarchy.

Content builders

Some screens can have dynamic content, for example Tabs. Therefore the framework provides ScreenBuilder protocol:

public protocol ScreenBuilder: PathProvider {
    associatedtype Content
    associatedtype Context

    func makeContent<From>(_ context: Context, router: Router<From>) -> Content where From: Screen, From.PathFrom == PathFrom
}

And of course for such instances is necessary Swift's result builder:

@resultBuilder
public struct ContentBuilder {}

Cross-platform

Framework API has cross-platform namespaces:

public enum Win {} /// Window implementations
public enum Nav {} /// Navigation implementations
public enum Tab {} /// Tabs implementations
extension Nav {
    public enum Push { /// Push implementations
        public enum Pop {} /// Pop implementations
    }
}
public enum Presentation { /// Present implementations
    public enum Dismiss {} /// Dismiss implementations
}

For convenience, the framework provides protocols that enable typealiases to the nested types: UIKitNamespace, AppKitNamespace, SwiftUINamespace. Apply one of them and you can write crossplatform code where:

Screens:

  • Window - a screen container that wraps a initial screen of your app.
  • Navigation - a screen container that creates navigation stack.
  • Tabs - a content screen that organize multiple screens to tab view interface.

Transitions

  • Push - a transition that pushes a new screen onto the navigation stack, with the corresponding Pop transition.
  • Present - a transition that presents a new screen, covering the current screen, with the corresponding Dismiss transition.

SwiftUI

Supported screens:

  • Window
  • Navigation
  • Tabs

Supported transitions:

  • Push/Pop
  • Present/Dismiss

UIKit

Supported screens:

  • Window
  • Navigation
  • Tabs

Supported transitions:

  • Push/Pop
  • Present/Dismiss

AppKit

Supported screens:

  • Window
  • Tabs

Supported transitions:

  • Present/Dismiss

Best Practices

Screen appearance You can define a protocol that will describe a screen appearance. So, you will create a single source of truth.
protocol ScreenAppearance {
    var title: String { get }
    var tabImage: Image? { get }
    ...
}
extension ScreenAppearance {
    var tabImage: Image? { nil }
    ...
}
extension ScreenAppearance where Self: ContentScreen {
    func applyAppearance(_ content: Content) {
        /// configure content
    }
}
protocol ScreenContent {
    associatedtype Route: Screen
    var router: Router<Route> { get }
}
extension ScreenContent where Route.PathFrom: ScreenAppearance {
    func prepareAppearance() {
        router[next: \.self].applyAppearance(self)
    }
}
Universal transitions There is screens that should available everywhere. So, you can extend `ContentScreen` protocol.
struct Alert: ContentScreen {
    /// alert screen implementation
}
extension ContentScreen {
    var alert: Present<Self, Alert> { get }
}

/// now you can show alert from any screen
router.move(\.alert, from: self, with: "Hello world")

Installation

pod 'ScreenUI'
.package(url: "https://github.com/k-o-d-e-n/ScreenUI", from: "1.1.0")

Author

Denis Koryttsev, @k-o-d-e-n, [email protected]

License

ScreenUI is available under the MIT license. See the LICENSE file for more info.

You might also like...
An App-specific Simple Routing Library

TrieRouter An App-specific Simple Routing Library Usage let r = Router() r.addRoute("tbx://index") { _ in print("root") } r.addRoute("tbx://intTes

Native, declarative routing for SwiftUI applications.

SwiftfulRouting 🕊 Native, declarative routing for SwiftUI applications Setup time: 1 minute Sample project: https://github.com/SwiftfulThinking/Swift

PassDrop is a fully-featured secure password management system, compatible with the free KeePass 1.x (Classic) and multi-platform KeePassX desktop applications.

passdrop This is a modern, updated build of Rudis Muiznieks's PassDrop application. PassDrop is a fully-featured secure password management system, co

OTAtomics - Multi-platform Swift thread-safe atomics library

OTAtomics Multi-platform Swift thread-safe atomics library. The library has full

A multi-platform SwiftUI component for tabular data
A multi-platform SwiftUI component for tabular data

SwiftTabler A multi-platform SwiftUI component for tabular data. NOTE this component is BRAND NEW and under active development. If you need stability,

nds4ios is a port of the multi-platform Nintendo DS emulator, DeSmuME to iOS.

nds4ios Supports iOS 6 to iOS 9. nds4ios is a port of the multi-platform Nintendo DS emulator, DeSmuME to iOS. Currently, emulation is powered by a th

Auto scrollable multi platform header menu usually used in food delivery applications - SwiftUI & Combine
Auto scrollable multi platform header menu usually used in food delivery applications - SwiftUI & Combine

Auto scrollable header menu - SwiftUI & Combine Features Auto scrollable up menu while navigating the list up and down Navigate to any section from up

Customizable multi platform menu bar component with the dark and light scheme support - SwiftUI
Customizable multi platform menu bar component with the dark and light scheme support - SwiftUI

Menu bar component (SwiftUI) Features Observing menu selection changes via generic PreferenceKey The color intensity automatically adjusts depending o

⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading
⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your appl

Easily generate cross platform Swift framework projects from the command line
Easily generate cross platform Swift framework projects from the command line

SwiftPlate Easily generate cross platform Swift framework projects from the command line. SwiftPlate will generate Xcode projects for you in seconds,

Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift.

Futures Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift. It's lightweight, fast, and easy to understa

WCDB is a cross-platform database framework developed by WeChat.
WCDB is a cross-platform database framework developed by WeChat.

WCDB 中文版本请参看这里 WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It's currently available on iOS,

iOS/macOS Cross-platform Ark-Ecosystem Framework in Swift | Powered by Ѧrk.io |
iOS/macOS Cross-platform Ark-Ecosystem Framework in Swift | Powered by Ѧrk.io |

a macOS & iOS Swift Framework for Ark.io. What is ARKKit? ARKKit is wrapper for interacting with the Ark Ecosystem. It is written purely in Swift 4.0,

A cross-platform SwiftUI-like framework built on SwiftGtk.

SwiftGtkUI A SwiftUI-like framework for creating cross-platform apps in Swift. It uses SwiftGtk as its backend. NOTE: SwiftGtkUI does not attempt to r

SwiftCrossUI - A cross-platform SwiftUI-like UI framework built on SwiftGtk.

SwiftCrossUI A SwiftUI-like framework for creating cross-platform apps in Swift. It uses SwiftGtk as its backend. This package is still quite a work-i

Multi image downloader with priority in Swift
Multi image downloader with priority in Swift

Vulcan Multi image downloader with priority in Swift Features Very light Multi image download with priority Caching images Pure Swift Composable image

Multi-dimensional Swift math

Upsurge Upsurge implements multi-dimensional data structures and operations. It brings numpy-like operations to Swift. Upsurge no longer supports DSP

Numpy-like library in swift. (Multi-dimensional Array, ndarray, matrix and vector library)
Numpy-like library in swift. (Multi-dimensional Array, ndarray, matrix and vector library)

Matft Matft is Numpy-like library in Swift. Function name and usage is similar to Numpy. Matft Feature & Usage Declaration MfArray MfType Subscription

Provides a SwiftUI multi-line TextView implementation including support for auto-sizing. (iOS)

TextView Also available as a part of my SwiftUI+ Collection – just add it to Xcode 13+ Provides a SwiftUI multi-line TextView implementation with supp

Releases(1.1.1)
Owner
Koryttsev Denis
Koryttsev Denis
sRouting - The lightweight navigation framework for SwiftUI.

sRouting The lightweight navigation framework for SwiftUI. Overview sRouting using the native navigation mechanism in SwiftUI. It's easy to handle nav

Shiro 8 Aug 15, 2022
A lightweight iOS mini framework that enables programmatic navigation with SwiftUI, by using UIKit under the hood.

RouteLinkKit A lightweight iOS mini framework that enables programmatic navigation with SwiftUI. RouteLinkKit is fully compatible with native Navigati

Αθανάσιος Κεφαλάς 4 Feb 8, 2022
zekunyan 608 Dec 30, 2022
LOL Champions app: a small Multi-Module demo application to demonstrate modern iOS application tech-stacks with a Multi-module and MVVM architecture

LOL Champions app: a small Multi-Module demo application to demonstrate modern iOS application tech-stacks with a Multi-module and MVVM architecture

Ahmed Nasser 5 Jun 9, 2022
Easy and maintainable app navigation with path based routing for SwiftUI.

Easy and maintainable app navigation with path based routing for SwiftUI.

Freek Zijlmans 278 Jun 7, 2021
Path based routing in SwiftUI

Easy and maintainable app navigation with path based routing for SwiftUI. With SwiftUI Router you can power your SwiftUI app with path based routing.

Freek 652 Dec 28, 2022
iOS routing done right. Handles both URL recognition and controller displaying with parsed parameters. All in one line, controller stack preserved automatically!

Developed and Maintained by Ipodishima Founder & CTO at Wasappli Inc. (If you need to develop an app, get in touch with our team!) So what is this lib

null 589 Dec 24, 2022
URL routing library for iOS with a simple block-based API

JLRoutes What is it? JLRoutes is a URL routing library with a simple block-based API. It is designed to make it very easy to handle complex URL scheme

Joel Levin 5.6k Jan 6, 2023
An iOS library to route API paths to objects on client side with request, mapping, routing and auth layers

WANetworkRouting Developed and Maintained by ipodishima Founder & CTO at Wasappli Inc. Sponsored by Wisembly A routing library to fetch objects from a

null 10 Nov 20, 2022
RoutingKit - Routing library With Swift

RoutingKit Usage struct MessageBody: Body { typealias Response = String

HZ.Liu 3 Jan 8, 2022