Models UI navigation patterns using TCA

Overview

CI codecov

Composable Navigation

The Composable Navigation is a Swift Package that builds on top of The Composable Architecture (TCA, for short). It models UI navigation patterns using TCA. The corresponding navigation views are implemented in UIKit.

The concept is inspired by the coordinator pattern as it allows a clean separation between individual screens and the logic that ties those screens together. In a TCA world, a coordinator is represented by a state composed of its child views' sub-states and the navigation state. A reducer would then be able to manage the navigation state similar as a coordinator would do by observing actions from child views and presenting/dismissing other screens.

Features

Modal navigation

ModalNavigation models state and actions of a commonly used modal view presentation. Views can be presented with a certain style and dismissed. The ModalNavigationController listens to state changes and presents the provided views accordingly. Any state changes are reflected by the controller using UIKit.

Setting the current navigation item to a different screen will result in dismissing the old screen and presenting the new screen. Even changes to only the presentation style are reflected accordingly.

It also supports automatic state updates for pull-to-dismiss for views presented as a sheet.

This example shows how a modal-navigation could be implemented using an enum:

struct Onboarding {
    enum Screen {
        case login
        case register
    }

    struct State: Equatable {
        var modalNavigation = ModalNavigation<Screen>.State()
        ...
    }

    enum Action: Equatable {
        case modalNavigation(ModalNavigation.Action)
        ...
    }

    private static let privateReducer = Reducer<State, Action, Environment> { state, action, environment in
        switch action {
        case .loginButtonPressed:
            return Effect(value: .modalNavigation(.presentFullScreen(.login)))
        case .anotherAction:
            return Effect(value: .modalNavigation(.dismiss))
        }
        return .none
    }

    static let reducer: Reducer = Reducer.combine([
        ModalNavigation<Screen>.reducer()
            .pullback(
                state: \.modalNavigation,
                action: /Action.modalNavigation,
                environment: { _ in () }
            ),
        privateReducer
    ])
}

Stack navigation

StackNavigation models state and actions of a stack-based scheme for navigating hierarchical content. Views can be pushed on the stack or popped from the stack. Even mutations to the whole stack can be performed. The StackNavigationController listens to state changes and updates the view stack accordingly using UIKit.

It also supports automatic state updates for popping items via the leading-edge swipe gesture or the long press back-button menu.

This example shows how a series of text inputs could be implemented:

struct Register {
    enum Screen {
        case email
        case firstName
        case lastName
    }

    struct State: Equatable {
        var stackNavigation = StackNavigation<Screen>.State(items: [.email])
        ...
    }

    enum Action: Equatable {
        case stackNavigation(StackNavigation.Action)
        ...
    }

    private static let privateReducer = Reducer<State, Action, Environment> { state, action, environment in
        switch action {
        case .emailEntered:
            return Effect(value: .stackNavigation(.pushItem(.firstName)))
        case .firstNameEntered:
            return Effect(value: .stackNavigation(.pushItem(.lastName)))
        ...
        }
        return .none
    }

    static let reducer: Reducer = Reducer.combine([
        StackNavigation<Screen>.reducer()
            .pullback(
                state: \.stackNavigation,
                action: /Action.stackNavigation,
                environment: { _ in () }
            ),
        privateReducer
    ])
}

Tab navigation

TabNavigation models state and actions of a tab-based scheme for navigating multiple child views. The active navigation item can be changed by setting a new item. Even mutations to child item can be performed (e.g. changing the tab order). The TabNavigationController listens to state changes and updates the selected view accordingly.

Example:

struct Root {
    enum Screen: CaseIterable {
        case home
        case list
        case settings
    }

    struct State: Equatable {
        var tabNavigation = TabNavigation<Screen>.State(
            items: Screen.allCases,
            activeItem: .home
        )
        ...
    }

    enum Action: Equatable {
        case tabNavigation(TabNavigation.Action)
        ...
    }

    private static let privateReducer = Reducer<State, Action, Environment> { state, action, environment in
        switch action {
        case .goToSettings:
            return Effect(value: .tabNavigation(.setActiveItem(.settings)))
        ...
        }
        return .none
    }

    static let reducer: Reducer = Reducer.combine([
        TabNavigation<Screen>.reducer()
            .pullback(
                state: \.tabNavigation,
                action: /Action.tabNavigation,
                environment: { _ in () }
            ),
        privateReducer
    ])
}

ViewProviding

The ViewProvider creates a view according to the given navigation item. It implements ViewProviding which requires the type to create a Presentable (e.g. a SwiftUI View or a UIViewController) for a given navigation item.

Navigation container views (like StackNavigationController) expect a ViewProvider. It is used to create new views on demand, for example when a new item is pushed on the navigation stack. Navigation container views create new views only when necessary. For example the StackNavigationController will reuse the already created view for .b if the stack of navigation items changes like this: [.a, .b, .c] -> [.x, .y, .b,]

struct ViewProvider: ViewProviding {
    let store: Store
    
    func makePresentable(for navigationItem: Screen) -> Presentable {
        switch navigationItem {
        case .a:
            return ViewA(store: store.scope( ... ))
        case .b:
            return ViewB(store: store.scope( ... ))
        }
    }
}

Usage

A navigation container view can be integrated like any other UIViewController in your app.

This is an example of a TabNavigationController in a SceneDelegate:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
	var window: UIWindow?

    lazy var store: Store = ...

	func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
		guard let windowScene = scene as? UIWindowScene else {
			return
		}
		
		let controller = TabNavigationController(
			store: store.scope(
				state: \.tabNavigation,
				action: App.Action.tabNavigation
			),
			viewProvider: App.ViewProvider(store: store)
		)

		let window = UIWindow(windowScene: windowScene)
		window.rootViewController = controller
		self.window = window
		window.makeKeyAndVisible()
	}

    ...
}

Showcases

You can find multiple showcases in the Examples project.

The example app hosts multiple showcases (and UI tests), to run one of the showcase you need to changes the variable showcase in SceneDelegate.swift.

...
// 👉 Choose showcase 👈
let showcase: Showcase = .advanced
...

License

This library is released under the MIT license. See LICENSE for details.

You might also like...
Cordova/Phonegap plugin for launching today's most popular navigation/ride apps to navigate to a destination.
Cordova/Phonegap plugin for launching today's most popular navigation/ride apps to navigate to a destination.

Launch Navigator Cordova/Phonegap Plugin Cordova/Phonegap plugin for launching today's most popular navigation/ride apps to navigate to a destination.

Make SwiftUI Navigation be easy

VNavigator VNavigator is a clean and easy-to-use navigation in SwiftUI base on UINavigationController in UIKit Installation From CocoaPods CocoaPods i

Tools for making SwiftUI navigation simpler, more ergonomic and more precise.
Tools for making SwiftUI navigation simpler, more ergonomic and more precise.

SwiftUI Navigation Tools for making SwiftUI navigation simpler, more ergonomic and more precise. Motivation Tools Navigation overloads Navigation view

Simple custom navigation bar by swift

YoNavBarView Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installation YoNav

Easily hide and show a view controller's navigation bar (and tab bar) as a user scrolls
Easily hide and show a view controller's navigation bar (and tab bar) as a user scrolls

HidingNavigationBar An easy to use library (written in Swift) that manages hiding and showing a navigation bar as a user scrolls. Features Usage Custo

Simple and integrated way to customize navigation bar experience on iOS app.

NavKit Simple and integrated way to customize navigation bar experience on iOS app. It should save our time that we usually use to make abstraction of

Replicating the 'clear' navigation bar style of the iOS 12 Apple TV app.
Replicating the 'clear' navigation bar style of the iOS 12 Apple TV app.

TONavigationBar TONavigationBar is an open-source subclass of UINavigationBar that adds the ability to set the background content of the navigation ba

SwiftUINavigation provides UIKit-like navigation in SwiftUI

SwiftUINavigation About SwiftUINavigation provides UIKit-like navigation in Swif

SwiftUINavigator: a lightweight, flexible, and super easy library which makes SwiftUI navigation a trivial task
SwiftUINavigator: a lightweight, flexible, and super easy library which makes SwiftUI navigation a trivial task

The logo is contributed with ❤️ by Mahmoud Hussein SwiftUINavigator is a lightwe

Comments
  • Existing UINavigationController

    Existing UINavigationController

    • Add debug warning if UINavigationController's delegate is unsigned from StackNavigationHandler
    • Add debug warning if UITabBarController's delegate is unsigned from TabNavigationHandler
    • Add support for UINavigationControllers with existing view controllers
    opened by heinzl 0
Releases(3.0.0)
  • 3.0.0(Dec 5, 2022)

    What's Changed

    • Support reducer protocol by @heinzl in https://github.com/heinzl/swift-composable-navigation/pull/7

    Full Changelog: https://github.com/heinzl/swift-composable-navigation/compare/2.1.0...3.0.0

    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Dec 3, 2022)

    What's Changed

    • Modal state management example by @heinzl in https://github.com/heinzl/swift-composable-navigation/pull/4
    • Update TCA to v0.40.2 by @heinzl in https://github.com/heinzl/swift-composable-navigation/pull/5
    • Existing UINavigationController by @heinzl in https://github.com/heinzl/swift-composable-navigation/pull/6

    Full Changelog: https://github.com/heinzl/swift-composable-navigation/compare/2.0.0...2.1.0

    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Dec 22, 2021)

    What's Changed

    • Add explicit 'animated' parameter to navigation actions (default true) by @heinzl in https://github.com/heinzl/swift-composable-navigation/pull/3

    Full Changelog: https://github.com/heinzl/swift-composable-navigation/compare/1.0.3...2.0.0

    Source code(tar.gz)
    Source code(zip)
Owner
Michael Heinzl
Michael Heinzl
A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles while pushing or popping a view controller for all orientations. And you don't need to write any line of code for it, it all happens automatically.

Zhouqi Mo 3.3k Dec 21, 2022
An iOS view-controller navigation management. No inherit, using one line code to integrate.

KGNavigationBar Example An iOS view-controller navigation management. No inherit, using one line code to integrate. 一个 iOS 控制器导航管理库. 无需继承, 一行代码即可实现集成。

VanJay 5 Sep 6, 2021
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
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
FlowStacks allows you to hoist SwiftUI navigation and presentation state into a Coordinator

FlowStacks allow you to manage complex SwiftUI navigation and presentation flows with a single piece of state. This makes it easy to hoist that state into a high-level coordinator view. Using this pattern, you can write isolated views that have zero knowledge of their context within the navigation flow of an app.

John Patrick Morgan 471 Jan 3, 2023
Change SwiftUI Navigation Bar Color for different View

SwiftUINavigationBarColor Change SwiftUI NavigationBar background color per screen. Usage For NavigationBarColor to work, you have to set the Navigati

Hai Feng Kao 18 Jul 15, 2022
🧭 SwiftUI navigation done right

?? NavigationKit NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. ?? Installation ?? Swift Package Manager Usi

Alex Nagy 152 Dec 27, 2022
Navigation helpers for SwiftUI applications build with ComposableArchitecture

Swift Composable Presentation ?? Description Navigation helpers for SwiftUI applications build with ComposableArchitecture. More info about the concep

Dariusz Rybicki 52 Dec 14, 2022
Powerful navigation in the Composable Architecture via the coordinator pattern

TCACoordinators The coordinator pattern in the Composable Architecture TCACoordinators brings a flexible approach to navigation in SwiftUI using the C

John Patrick Morgan 231 Jan 7, 2023
A wrapper for NavigationView and NavigationLink that makes programmatic navigation a little friendlier.

NavigatorKit A wrapper for NavigationView and NavigationLink that makes programmatic navigation a little friendlier. NavigatorKit is an opinionated wr

Gyuri Grell 2 Jun 16, 2022