✨ LinkNavigator is a library that helps you easily navigate between pages in SwiftUI.
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!
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)
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)
LinkNavigator provides 2 Example Apps.
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.
}
}
}
}
navigator
property 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.
For example, you can put it in ViewModel
or Environment
.
struct HomePage: View {
let navigator: LinkNavigatorType
var body: some View {
...
}
}
RouteBuilder
protocol 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)
}
}
}
}
LinkNavigator supports Swift Package Manager.
File
menu at the top of Xcode -> Select Add Packages...
.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"])
]
)
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?
- You can customize the WrappingController. I will provide an example code for customization.
```swift
import SwiftUI
public final class DebugWrappingViewController<Content: View>: UIHostingController<Content>, 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
}
This library is released under the MIT license. See LICENSE for details.