A lightweight iOS mini framework that enables programmatic navigation with SwiftUI, by using UIKit under the hood.

Overview

RouteLinkKit

A lightweight iOS mini framework that enables programmatic navigation with SwiftUI. RouteLinkKit is fully compatible with native NavigationLinks, while also supporting features available when using UINavigationController.

Version History

Version Changes
0.8 Pre-release.

🛠 Features

RouteLinkKit has the following features:

  • Supports vanilla SwiftUI lifecycle apps.
  • Supports native NavigationLinks and state driven navigation.
  • Supports mixing declarative and programmatic (imperative) navigation schemes.
  • Uses UIKit based navigation, and exposes the navigation controller in use.

Limitations:

  • Uses UIKit based navigation, which limits any products to work only on platforms that support UIKit.
  • Minor set up and configuration may be required.
  • All routes managed by a specific router must be of the same base type.
  • Limited testing capability due to the fact that internally it uses native NavigationLink views.

❗️ Disclaimer

This mini framework started as a proof of concept, and has only been tested with iOS and iPadOS, using the SwiftUI App lifecycle. As a result, it is provided as is and without ANY warranty of any kind. If you plan to use this framework, especially in producion code, please do a round of testing before commiting to it.

📦 Instalation

Swift Package

You may add RouteLinkKit as a Swift Package dependency using Xcode 11.0 or later, by selecting File > Swift Packages > Add Package Dependency... or File > Add packages... in Xcode 13.0 and later, and adding the url below:

https://github.com/athankefalas/RouteLinkKit.git

Manually

You may also install this framework manually by downloading the RouteLinkKit project and including it in your project.

⚡️ Quick Setup

This section will contain an example setup, for a app that displays a list of products and their details.

1. Define a set of Routes

A route is a type that can represent the routes available in a specific navigation hierarchy. Each route must be uniquely identifiable by it's hash value. To define a route you can create an enum, struct or class and conform to the RouteRepresenting protocol. In the context of the test products app we may define the available routes as the enum below:

enum ProductRoutes: RouteRepresenting {
    case productsList
    case selectedProduct(productId: Int)
    case viewingProductDescription(productId: Int)
}

2. Define a ViewComposer

A view composer is a type that accepts a route and composes a view to visually represent that specific route. The view composer must know how to compose and create new views for each of the routes it supports. To define a view composer create a class and conform to the ViewComposer protocol. The only requirement of the protocol is to define a function that accpets a generic route and returns a type-erased view. If you want to dynamically compose views at runtime based onthe current context of your app you may do it in the view composer.

class ProductsViewComposer: ViewComposer {
    
    func composeView<Route>(for route: Route) -> AnyView where Route : RouteRepresenting {
        guard let productsRoute = route as? ProductRoutes else {
            assertionFailure("ProductsViewComposer: Failed to compose a view for route '\(route)' of type '\(type(of: route))'.")
            return AnyView( EmptyView() )
        }
        
        switch route {
        case .productsList:
            return AnyView( ProductsView() )
        case .selectedProduct(productId: let productId):
            return AnyView( ProductDetailsView(productId: productId) )
        case .viewingProductDescription(productId: let productId):
            return AnyView( ProductDescriptionView(productId: productId) )
        }
    }
}

3. Create a router

A router is a type that can be used to manage and perform programmatic navigation. The router holds a reference to the UIKit navigation controller that is currently in use and the view composer for the navigation hierarchy it manages. Finally, a router also contains a property that defines it's root route. The type of the root route is assumed to be the base type used for all routes in the navigation hierarchy and must conform to the RouteRepresenting route.

class ProductsRouter: Router {
    let navigationController = UIRoutingNavigationController()
    let routeViewComposer: ViewComposer = ProductsViewComposer()
    
    let rootRoute = ProductRoutes.productsList
}

3.1 Injecting Routers in Views

The router is also injected in the environment values of all views that are descendants of a RoutedNavigationView and can be used in the following way:

struct SomeView: View {

    @Environment(\.routing) var routing: RoutingEnvironment

    var body: some View { ..... }

    func popToRoot() {
        // Use common methods
        routing.router?.dismissToRoot(animated: true)
    }
    
    func showTrail() {
        guard let router = routing.router as? ProductsRouter else {
            return
        }
        
        // Use common Route-dependant methods
        routing.router?.dismiss(to: .productsList)
        routing.router?.show(route: .productsList, animated: false)
        routing.router?.show(trail: [.productsList, .selectedProduct(productId:0), .viewingProductDescription(productId:0)])
    }

    func showTrailCustom() {
        // Drop down to UIKit to provide more custom functionality
        routing.router?.navigationController.setViewControllers([...], animated: true)
    }
}

3.2 Router API

As mentioned above routers came with common navigation functions built-in, even though you can still provide your own by extensions or by using the navigation controller instance if needed. The functions that are already implemented in routers are the following:

Function Description
dismiss(animated: Bool) Dismiss the top view in the current navigation stack.
dismissToRoot(animated: Bool) Dismiss all the views in the current navigation stack, until reaching the root view.
dismiss(to route: Route, animated: Bool) Dismiss the top views in the current navigation stack, until reaching the first instance of the specified route.
dismiss(before route: Route, animated: Bool) Dismiss the top views in the current navigation stack, until reaching the view just before the specified route.
restart(animated: Bool) Restart the current navigation stack by destroying all views, and restarts with a new instance of the root view.
show(route: Route, animated: Bool) Shows a new view for the specified route at the top of the current navigation stack.
present(route: Route, animated: Bool) Presents a new view modally for the specified route at the top of the current navigation stack.
show(trail path: [Route], animated: Bool) Shows a new trail of routes, replacing the current navigation stack.

4. Replace NavigationView with RoutedNavigationView

One of the two SwiftUI components of RouteLinkKit is RoutedNavigationView that simply replaces the native NavigationView with a custom implementation that uses a UINavigationController subclass for navigation and to provide the navigation bar. The RoutedNavigationView behaves the same as the native NavigationView and can even perform native navigation links if needed. The main difference is that the content of a routed navigation view is automatically created by the router.

struct TestProductsApp: App {
    
    @StateObject private var router = ProductsRouter()
    
    var body: some Scene {
        WindowGroup {
            // Replace:
            NavigationView {
              ProductsView()
            }
            // With:
            RoutedNavigationView(using: $router)
        }
    }
 }

5. Replace NavigationLink with RouteLink (Optional)

In cases where the destination of navigation links needs to be resolved dynamically at runtime, based on the current context of your app, use a RouteLink instead of a NavigationLink. For any dynamic route you require create the appropriate view in the view composer. The way that RouteLink is implemented internally uses the native NavigationLink with the main difference between the two being that RouteLink doesn't require that the destination to be defined at compile time.

struct ProductsView: View {
    @State private var products: [Product] = Product.previewProducts
    @State private var selection: Product?
    
    var body: some View {
        List {
            ForEach(products, id: \.self) { product in
                // If no dynamic view resolution is required, use NavigationLink.
                // It will work normally when placed within RoutedNavigationViews.
                NavigationLink(tag: product, selection: $selection) {
                    ProductDetailsView(productId: product.id)
                } label: {
                    Text(product.title)
                }
                
                // If dynamic view resolution is required, then use RouteLink.
                // The view can then be dynamically resolved at runtime by using
                // the provided View Composer.
                RouteLink(tag: product, selection: $selection, to: ProductRoutes.selectedProduct(productId: product.id)) {
                    Text(product.title)
                }
            }
        }
    }
}

🧩 Extension Points

The RouteLinkKit framework offers a possible extension point, that can help to modify the behaviour of the underlying UIKit UINavigationController. The class used in RouteLinkKit is a subclass of UINavigationController, specifically configured to be used with SwiftUI called UIRoutingNavigationController and it's default behaviour can be easily modified by subclassing.

If more custom behaviour is required you can duplicate and modify the RoutedNavigationLink and RouteLink views, which by default only provide the functionality provided natively by SwiftUI.

You might also like...
🧭 SwiftUI navigation done right

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

Navigation helpers for SwiftUI applications build with ComposableArchitecture
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

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

Custom navigation swiftui NavigationLink NavigationView

Custom navigation swiftui Experimenting with navigation link. if you find this idea interesting you can take and expend it into a more powerful soluti

Backported SwiftUI navigation APIs introduced in WWDC22

Navigation Backport This package uses the navigation APIs available in older SwiftUI versions (such as NavigationView and NavigationLink) to recreate

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

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

Releases(1.1.0)
Owner
Αθανάσιος Κεφαλάς
Αθανάσιος Κεφαλάς
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
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 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
SwiftUINavigation provides UIKit-like navigation in SwiftUI

SwiftUINavigation About SwiftUINavigation provides UIKit-like navigation in Swif

Bhimsen Padalkar 1 Mar 28, 2022
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

OpenBytes 22 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
Models UI navigation patterns using TCA

Composable Navigation The Composable Navigation is a Swift Package that builds on top of The Composable Architecture (TCA, for short). It models UI na

Michael Heinzl 41 Dec 14, 2022
Simple iOS app to showcase navigation with coordinators in SwiftUI + MVVM.

SimpleNavigation Simple iOS app to showcase the use of the Coordinator pattern using SwiftUI and MVVM. The implementation is as easy as calling a push

Erik Lopez 7 Dec 6, 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