
[![Swift Version][swift-image]][swift-url] [![License][license-image]][license-url]
[swift-url]: https://swift.org/ [swift-image]:https://img.shields.io/badge/swift-5.5-orange.svg [license-image]: https://img.shields.io/badge/License-MIT-blue.svg [license-url]: LICENSE
- Concept
✨ LinkNavigator is a library that helps you easily navigate between pages in SwiftUI.
- LinkNavigator provides an intuitive syntax for navigating pages via URL path-like expressions.
- You can easily go to any page with the deep-link processing style.
- You can inject parameters with page transition.
- LinkNavigator is designed for use in Uni-directional Architecture such as MVI design pattern or The Composable Architecture from pointfreeco, but it can be used in other architectures as well.
- Translations
The following translations of this README have been contributed by members of the community:
If you'd like to contribute a translation, please open a PR with a link to a Gist!- Basic Usage
- push one or many pages.
navigator.next(paths: ["page1", "page2"], items: [:], isAnimated: true)
- pop one or many pages.
navigator.remove(paths: ["pageToRemove"])
- back to the prior page or dismiss modal simply.
navigator.back(isAnimated: true)
- go to the page you want. If that page is already within navigation stack, go back to that page. Else if that page is not within stack, push new one.
navigator.backOrNext(path: "targetPage", items: [:], isAnimated: true)
- replace current navigation stack with new one.
navigator.replace(paths: ["main", "depth1", "depth2"], items: [:], isAnimated: true)
- open page as sheet or full screen cover.
navigator.sheet(paths: ["sheetPage"], items: [:], isAnimated: true)
navigator.fullSheet(paths: ["page1", "page2"], items: [:], isAnimated: true, prefersLargeTitles: false)
- close a modal and call completion closure.
navigator.close(isAnimated: true) { print("modal dismissed!") }
- show a system 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)
- Advanced Usage
- edit complicated paths and use it.
// current navigation 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)
- you can choose modal presentation styles for iPhone and iPad respectively.
navigator.customSheet(
paths: ["sheetPage"],
items: [:],
isAnimated: true,
iPhonePresentationStyle: .fullScreen,
iPadPresentationStyle: .pageSheet,
prefersLargeTitles: .none)
- forcely reload the last page behind the modal. This is useful when you need to call the onAppear(perform:)) again.
navigator.rootReloadLast(items: [:], isAnimated: false)
- Example
LinkNavigator provides 2 Example Apps.

- Getting Started
Step 1
- To install LinkNavigator in your SwiftUI project, you need to implement 4 files.
- You can freely edit the type names. In the following examples, simple names are used for clarity.
- Describe in order: AppDependency -> AppRouterGroup -> AppDelegate -> AppMain
// AppDependency.swift
// A type that manages external dependencies.
import LinkNavigator
struct AppDependency: DependencyType { } // you need to adopt DependencyType protocol here.
// AppRouterGroup.swift
// A type that manages the pages you want to go with LinkNavigator.
import LinkNavigator
struct AppRouterGroup {
var routers: [RouteBuilder] {
[
HomeRouteBuilder(), // to be implemented in Step 3
Page1RouteBuilder(),
Page2RouteBuilder(),
Page3RouteBuilder(),
Page4RouteBuilder(),
]
}
}
// AppDelegate.swift
// A type that manages the navigator injected with external dependencies and pages.
import SwiftUI
import LinkNavigator
final class AppDelegate: NSObject {
var navigator: LinkNavigator {
LinkNavigator(dependency: AppDependency(), builders: AppRouterGroup().routers)
}
}
extension AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
true
}
}
// AppMain.swift
// A type that sets the starting page of the Application.
import SwiftUI
import LinkNavigator
@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: [:]) // the argument of 'paths' becomes starting pages.
.onOpenURL { url in
// in case you need deep link navigation,
// deep links should be processed here.
}
}
}
}
Step 2
- Add a
navigatorproperty inside the page struct type, so that it is injected when initialized. - Depending on the characteristics of the architecture, freely change the position of the navigator property and use it.
ViewModel or Environment.
struct HomePage: View {
let navigator: LinkNavigatorType
var body: some View {
...
}
}
Step 3
- Create a struct type adopting the
RouteBuilderprotocol for every page. - RouteBuilder structs created in this way are collected and managed in the AppRouterGroup type.
import LinkNavigator
import SwiftUI
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)
}
}
}
}
- Installation
LinkNavigator supports Swift Package Manager.
Filemenu at the top of Xcode -> SelectAdd Packages....- Enter "https://github.com/interactord/LinkNavigator.git" in the Package URL field to install it.
- or, add the following in the
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"])
]
)
- Extra
- Q: How can I use large titles in SwiftUI?
/// in AppMain.swift (MVI)
/// To use for route navigation, set the prefersLargeTitles parameter to true in the launch method.
navigator
.launch(paths: ["home"], items: [:], prefersLargeTitles: true)
/// in HomeView.swift (MVI)
/// To specify the display mode of the navigation bar title, use the navigationBarTitleDisplayMode (.line, .large, .automatic) in the SwiftUI screen of each screen.
ScrollView {
....
}
.navigationBarTitleDisplayMode(.large)
.navigationTitle("Home")
/// If you want to use it in fullSheet or customSheet,
/// Home.intent (MVI)
/// To enable large titles, set the prefersLargeTitles variable to true. To maintain the current settings, use .none.
navigator.fullSheet(paths: ["page1", "page2"], items: [:], isAnimated: true, prefersLargeTitles: true)
- Q: I'm wondering how to apply IgnoringSafeArea to a specific part or the entire screen if I want to?
navigator
.launch(paths: ["home"], items: [:], prefersLargeTitles: true)
/// - Note:
/// If you are using the ignoresSafeArea property to ignore the safe area on an internal screen,
/// please add the corresponding code to the part where you first execute the LinkNavigator.
.ignoresSafeArea()
- Q: In the view controller, I need to handle various tasks such as navigation or calling Firebase events when calling the screen. How should I handle it?
swift
import SwiftUI
public final class DebugWrappingViewController: UIHostingController, MatchPathUsable {
// MARK: Lifecycle
public init(
matchPath: String,
trackEventUseCase: TrackEventUseCase,
@ViewBuilder content: () -> Content)
{
self.matchPath = matchPath
self.eventSubscriber = eventSubscriber
self.trackEventUseCase = trackEventUseCase
super.init(rootView: content())
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
print("✂️ \(matchPath) deinit...")
}
// MARK: Public
public let matchPath: String
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = SystemColor.Background.Default.Base.getColor()
print("🚗 \(matchPath)")
trackEventUseCase.sendEvent(.screen(matchPath))
}
// MARK: Private
private let trackEventUseCase: TrackEventUseCase
}
``
- License
This library is released under the MIT license. See LICENSE for details.