Skip to content

Repository files navigation

Swift VersionLicense

- 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.

    letalertModel=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"]
    varnew= 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
    structAppDependency:DependencyType{} // you need to adopt DependencyType protocol here.
    // AppRouterGroup.swift
    // A type that manages the pages you want to go with LinkNavigator.
    import LinkNavigator
    structAppRouterGroup{varrouters:[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
    finalclassAppDelegate:NSObject{varnavigator:LinkNavigator{LinkNavigator(dependency:AppDependency(), builders:AppRouterGroup().routers)}}extensionAppDelegate: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
    @mainstructAppMain:App{@UIApplicationDelegateAdaptor(AppDelegate.self)privatevarappDelegatevarnavigator:LinkNavigator{
    appDelegate.navigator
    }varbody:someScene{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.

    structHomePage:View{letnavigator:LinkNavigatorTypevarbody:someView{...}}

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
    structHomeRouteBuilder:RouteBuilder{varmatchPath:String{"home"}varbuild:(LinkNavigatorType,[String:String],DependencyType)->MatchingViewController?{{ navigator, items, dependency inreturnWrappingController(matchPath: matchPath){HomePage(navigator: navigator)}}}}

- Installation

LinkNavigator supports Swift Package Manager.

letpackage=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.
import SwiftUI
publicfinalclassDebugWrappingViewController<Content:View>:UIHostingController<Content>,MatchPathUsable{
// MARK: Lifecycle
publicinit(
matchPath:String,
trackEventUseCase:TrackEventUseCase,@ViewBuilder content:()->Content){self.matchPath = matchPath
self.eventSubscriber = eventSubscriber
self.trackEventUseCase = trackEventUseCase
super.init(rootView:content())}requiredinit?(coder _:NSCoder){fatalError("init(coder:) has not been implemented")}deinit{print("✂️ \(matchPath) deinit...")}
// MARK: Public
publicletmatchPath:Stringpublicoverridefunc viewDidLoad(){
super.viewDidLoad()
view.backgroundColor =SystemColor.Background.Default.Base.getColor()print("🚗 \(matchPath)")
trackEventUseCase.sendEvent(.screen(matchPath))}
// MARK: Private
privatelettrackEventUseCase:TrackEventUseCase}

- License

This library is released under the MIT license. See LICENSE for details.

About

🌊 Easy & Powerful navigation library in SwiftUI

Topics

Resources

Stars

433 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages