Profile
Back to NewsBack
GitHub Trending 7 min
Reader Mode
forXifLess/LinkNavigator: 🌊 Easy & Powerful navigation library in SwiftUI

forXifLess/LinkNavigator: 🌊 Easy & Powerful navigation library in SwiftUI

8 hours ago

[![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 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 { ... } }

Step 3

  • Create a struct type adopting the 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) } } } }


- Installation

LinkNavigator supports Swift Package Manager.

  • File menu at the top of Xcode -> Select Add 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?
1. Add the following code to the screen where LinkNavigator is first started (example: AppMain.swift). 2. Then, add the following example code. (Refer to the AppMain.swift example.)
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: 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.

Our Related Projects
Farm Fresh Kikapu

An e-commerce platform for farm-fresh produce - an online st...

View Project
Chat with me