LinkNavigator
repository·main·Indexed 19 days ago
https://github.com/forxifless/linknavigatorA SwiftUI navigation library that uses URL-like path strings to manage page stacks. It supports deep-linking, parameter injection, and complex navigation logic, designed for compatibility with Unidirectional Architectures such as MVI or The Composable Architecture (TCA). The library provides a comprehensive API for pushing, popping, replacing stacks, and managing modals, sheets, and alerts via RouteBuilder components.
What's inside LinkNavigator
- LinkNavigator is a SwiftUI library that enables intuitive page navigation using URL path-like expressions. It supports deep-link style navigation, parameter injection during transitions, and is designed to work well with Uni-directional Architectures like MVI or The Composable Architecture (TCA).
How RouteBuilder works
mainA
RouteBuilderis the bridge between a path string and a SwiftUI View. It requires two main properties:matchPath: A unique string that identifies the route.build: A closure that receives theLinkNavigatorType, a dictionary ofitems(parameters), and theDependencyType. It must return aMatchingViewController(usually aWrappingController) containing the SwiftUI view.
struct HomeRouteBuilder: RouteBuilder { var matchPath: String { "home" } var build: (LinkNavigatorType, [String: String], DependencyType) -> MatchingViewController? { { navigator, items, dependency in return WrappingController(matchPath: matchPath) { HomePage(navigator: navigator) } } } }Install LinkNavigator via Swift Package Manager
mainYou can install LinkNavigator using Xcode's Package Manager or by adding it to your
Package.swiftfile.Via Xcode UI:
- Go to the
Filemenu ->Add Packages.... - Enter
https://github.com/interactord/LinkNavigator.gitin the Package URL field.
Via Package.swift:
let package = Package( name: "MyPackage", products: [ .library( name: "MyPackage", targets: ["MyPackage"]), ], dependencies: [ .package(url: "https://github.com/interactord/LinkNavigator.git", .upToNextMajor(from: "0.6.1")) ], targets: [ .target( name: "MyPackage", dependencies: ["LinkNavigator"]) ] )- Go to the
Setup Guide: Implementing LinkNavigator in SwiftUI
mainTo integrate LinkNavigator, follow these three steps:
Step 1: Define Infrastructure
Implement four core components:
AppDependency: A type conforming toDependencyTypeto manage external dependencies.AppRouterGroup: A type that returns an array ofRouteBuilderobjects.AppDelegate: Manages theLinkNavigatorinstance, injecting dependencies and builders.AppMain: The@mainentry point that callsnavigator.launch(paths:items:)to start the app.
Step 2: Inject Navigator into Views
Add a
navigatorproperty (of typeLinkNavigatorType) to your page structs so it can be used for navigation within the view or its ViewModel.Step 3: Implement RouteBuilders
For every page, create a struct conforming to
RouteBuilder. Define itsmatchPathand abuildclosure that returns aMatchingViewController(typically usingWrappingController).// 1. AppDependency struct AppDependency: DependencyType { } // 2. AppRouterGroup struct AppRouterGroup { var routers: [RouteBuilder] { [HomeRouteBuilder(), Page1RouteBuilder()] } } // 3. AppDelegate final class AppDelegate: NSObject { var navigator: LinkNavigator { LinkNavigator(dependency: AppDependency(), builders: AppRouterGroup().routers) } } // 4. AppMain @main struct AppMain: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate var navigator: LinkNavigator { appDelegate.navigator } var body: some Scene { WindowGroup { navigator .launch(paths: ["home"], items: [:]) } } } // Step 2: View Injection struct HomePage: View { let navigator: LinkNavigatorType var body: some View { ... } } // Step 3: RouteBuilder struct HomeRouteBuilder: RouteBuilder { var matchPath: String { "home" } var build: (LinkNavigatorType, [String: String], DependencyType) -> MatchingViewController? { { navigator, items, dependency in return WrappingController(matchPath: matchPath) { HomePage(navigator: navigator) } } } }Basic Navigation API Reference
mainUse the
navigatorinstance to perform standard navigation tasks:- Push pages:
next(paths:items:isAnimated:)pushes one or many pages. - Pop pages:
remove(paths:)removes specific pages from the stack. - Go back:
back(isAnimated:)returns to the prior page or dismisses a modal. - Smart navigation:
backOrNext(path:items:isAnimated:)goes to a target page if it exists in the stack; otherwise, it pushes it. - Replace stack:
replace(paths:items:isAnimated:)replaces the entire current stack with new pages. - Modals:
sheet(paths:items:isAnimated:)opens a page as a sheet.fullSheet(paths:items:isAnimated:prefersLargeTitles:)opens a page as a full-screen cover.
- Dismissal:
close(isAnimated:completion:)closes the current modal. - Alerts:
alert(target:model:)shows a system alert.
// Push navigator.next(paths: ["page1", "page2"], items: [:], isAnimated: true) // Pop navigator.remove(paths: ["pageToRemove"]) // Back navigator.back(isAnimated: true) // Smart navigation navigator.backOrNext(path: "targetPage", items: [:], isAnimated: true) // Replace stack navigator.replace(paths: ["main", "depth1", "depth2"], items: [:], isAnimated: true) // Modals navigator.sheet(paths: ["sheetPage"], items: [:], isAnimated: true) navigator.fullSheet(paths: ["page1", "page2"], items: [:], isAnimated: true, prefersLargeTitles: false) // Close modal navigator.close(isAnimated: true) { print("modal dismissed!") } // Alert let alertModel = Alert( title: "Title", message: "message", buttons: [.init(title: "OK", style: .default, action: { print("OK tapped") })], flagType: .default) navigator.alert(target: .default, model: alertModel)- Push pages:
Advanced Navigation Techniques
mainLinkNavigator provides advanced control over the navigation stack and modal presentation:
- Path Manipulation: Use
navigator.range(path:)to get a sub-section of the current stack and combine it with new paths to perform complex replacements. - Root Navigation:
rootNext(paths:items:isAnimated:): Controls pages behind the current modal.rootBackOrNext(path:items:isAnimated:): Navigates the root stack while a modal is active.
- Custom Modal Styles:
customSheet(paths:items:isAnimated:iPhonePresentationStyle:iPadPresentationStyle:prefersLargeTitles:)allows setting different presentation styles for iPhone and iPad. - Reloading Root:
rootReloadLast(items:isAnimated:)forces a reload of the last page behind the modal (useful for triggeringonAppear).
// Edit complicated paths // current stack == ["home", "depth1", "depth2", "depth3"] // target stack == ["home", "depth1", "newDepth"] var new = navigator.range(path: "depth1") + ["newDepth"] navigator.replace(paths: new, items: [:], isAnimated: true) // Control pages behind modal navigator.rootNext(paths: ["targetPage"], items: [:], isAnimated: true) navigator.rootBackOrNext(path: "targetPage", items: [:], isAnimated: true) // Custom presentation styles navigator.customSheet( paths: ["sheetPage"], items: [:], isAnimated: true, iPhonePresentationStyle: .fullScreen, iPadPresentationStyle: .pageSheet, prefersLargeTitles: .none) // Reload last page behind modal navigator.rootReloadLast(items: [:], isAnimated: false)- Path Manipulation: Use