Skip to content

Open-Bytes/SwiftUINavigator

Repository files navigation

SwiftUINavigator Logo

The logo is contributed with โค๏ธ by Mahmoud Hussein

swift v5.3 iOS 13 macOS tvOS License

SwiftUINavigator is an on-the-fly approach for handling navigation in SwiftUI. It provides a familiar way of handling navigation similar to UIKit, where you can push or present a view controller without the need to declare links or local state variables. This approach is more flexible and allows for dynamic navigation, making it easier to build more complex navigation flows in your SwiftUI app. Unlike traditional navigation patterns in SwiftUI, SwiftUINavigator offers a more intuitive and straightforward way of managing your app's navigation hierarchy.

@EnvironmentObject private var navigator: Navigator

// Navigate to HomeScreen
navigator.navigate { HomeScreen() }

// Show sheet
navigator.navigate(type: .sheet(type: .normal)) {
  SheetView()
}
navigator.navigate(type: .sheet(type: .full)) { 
  SheetView()
}
navigator.navigate(type: .sheet(type: .fixedHeight(200))) {
  SheetView()
}

// Show dialog
navigator.navigate(type: .dialog) {
  DialogView()
}

// Show action sheet
navigator.presentActionSheet {
  ActionSheetView()
}

// Show confirmation dialog (Like action sheet starting from iOS 15)
navigator.presentConfirmationDialog(titleKey: "Color", titleVisibility: .visible) {
  ConfirmationDialogView()
}

// Show Alert
navigator.presentAlert {
  AlertView()
}

SwiftUINavigator Diagram

SwiftUINavigator Demo

Table of contents

Why?

Let's first explore the limitations of SwiftUI then explore the awesome features SwiftUINavigator provides.

SwiftUI Limitations

In SwiftUI, there are a lot of limitations:

  • Passing parameters to the view is not easy because you have to declare parameters locally and pass them to the view inside NavigationLink.

  • Can not navigate programmatically. You always have to declare the navigation links.

  • Inconsistent navigation when use NavigationLink, .sheet and .fullScreenCover

  • Can not ignore adding the view to the back stack.

  • Transition navigations can not be disabled or customized.

  • No navigation back to the root view.

  • Can not navigate to a view using a specific ID.

  • Customizing the navigation bar is not trivial.

SwiftUINavigator is awesome

SwiftUINavigator has a lot of awesome features. Here's some of these features:

  • Consistent navigation with screens, sheets, action sheets, dialogs, confirmation dialogs, and alerts.

  • You are free to pass parameters on the fly and no need to declare the parameters outside the navigation site.

  • Custom navigation transitions

  • Navigate to a view without adding it to the back stack.

  • Direct navigation with or without links

  • Present sheets without having to declare a sheet modifier.

  • Present dialogs easily.

  • Present alerts easily.

  • Present action sheets easily.

  • Dismiss to the previous view.

  • Dismiss to the root view.

  • Dismiss to a specific view using its ID.

  • Navigation Bars are built-in the library

Requirements

  • Swift 5.3+

Installation

Xcode Projects

Select File -> Swift Packages -> Add Package Dependency and enter https://github.com/Open-Bytes/SwiftUINavigator.

Swift Package Manager Projects

You can add SwiftUINavigator as a package dependency in your Package.swift file:

let package = Package(
    //...
    dependencies: [
      .package(url: "https://github.com/Open-Bytes/SwiftUINavigator", .upToNextMajor(from: "1.0.0"))
    ]
    //...
)

From there, refer to SwiftUINavigator in target dependencies:

targets: [
    .target(
        name: "YourLibrary",
        dependencies: [
          "SwiftUINavigator"
        ]
        //...
    ),
   // ...
]

Then simply import SwiftUINavigator wherever youโ€™d like to use the library.

โšก Usage

  1. Import SwiftUINavigator.
import SwiftUINavigator
  1. Declare NavView in the root view of the app.
NavView {
    HomeScreen()
}

NavView supports transition animations and other options. See NavView

  1. Navigate to your destination view:
// Declare navigator
@EnvironmentObject private var navigator: Navigator

// Navigate
navigator.navigate {
    SomeView()
}

For more details aboutNavigator, see Navigator

  • Using NavLink.
NavLink(destination: SomeView()) {
    // When this view is clicked, it will trigger 
    // the navigation and show ProductItemView
    ProductItemView()
}

For more details aboutNavigator, see NavLink

  1. Dismiss (navigate back) to the previous view programmatically (using Navigator) or using a link (using DismissLink).
  • Using Navigator
navigator.dismiss()
  • Or using DismissLink.
DismissLink {
    Label("Back", systemImage: "chevron.backward")
            .foregroundColor(.blue)
}

For more details about dismissing, see Dismissing (Navigation Back)

Action Sheet

navigator.presentActionSheet {
  ActionSheet(
          title: Text("Color"),
          buttons: [
            .default(Text("Red")),
            .default(Text("Green")),
            .default(Text("Blue")),
            .cancel()
          ]
  )
}

Confirmation Dialog

navigator.presentConfirmationDialog(titleKey: "Color", titleVisibility: .visible) {
  Group {
    Button(action: {}) {
      Text("Red")
    }
    Button(action: {}) {
      Text("Green")
    }
    Button(action: {}) {
      Text("Blue")
    }
  }
}

Alert

navigator.presentAlert {
    Alert(
        title: Text("Alert"),
        message: Text("Presented on the fly with SwiftUINavigator"),
        dismissButton: .cancel())
}

Dialog

        navigator.presentDialog(dismissOnTouchOutside: true) {
            VStack(spacing: 10) {
                Text("Dialog").bold()
                Text("Presented on the fly with SwiftUINavigator")
                Spacer().frame(height: 20)
                Button(action: {
                    navigator.dismissDialog()
                }) {
                    Text("Cancel")
                }
            }
                    .padding(15)
                    .background(Color.white)
                    .cornerRadius(10)
        }

Main Components

Component Description
NavView NavView is the alternative of SwiftUI NavigationView implementing
stack-based navigation with more control and flexibility
in handling the navigation
Navigator The Navigator class is the heart of the library as it includes all the navigation APIs. It's injected to any view as EnvironmentObject.
NavLink The alternative of NavigationLink. It's a wrapper of Navigator.
When clicked, it will navigate to the destination view with the specified navigation type.
DismissLink DismissLink is a view which dismisses the current view when tapped. It's a wapper for Navigator.dismiss()

NavView

NavView is the alternative of SwiftUI NavigationView implementing stack-based navigation with more control and flexibility in handling the navigation

The public initializers

public init(
        transition: NavTransition = .default,
        easeAnimation: Animation = .easeOut(duration: 0.2),
        showDefaultNavBar: Bool = true,
        @ViewBuilder rootView: () -> Root)

As you can see, you can customize the transition animation, easeAnimation and automatic navigation bar.

NavView(
        transition: .custom(push: .scale, pop: .slide),
        easeAnimation: .easeInOut) {
    HomeScreen()
}

For more details about NavTransition, see Navigation Transition Types

Navigator

The Navigator class is the heart of the library as it includes all the navigation APIs. It's injected to any view as EnvironmentObject.

@EnvironmentObject private var navigator: Navigator

You can use Navigator directly to navigate programmatically to any view with 4 options

  1. Push view (Regular Navigation)
navigator.navigate {
    ProductDetailScreen(item: item)
}
// OR
navigator.navigate(type: .push()) {
    ProductDetailScreen(item: item)
}

You can specify an ID for the pushed view navigate(SomeView(), type: .push(id: "Detail Screen")). Later, you can use this ID to navigate back to the view it's belonging to. See Dismissing (Navigation Back)

You can ignore adding the view to tha back stack navigate(SomeView(), type: .push(addToBackStack: false)). When you navigate back this view won't be displayed. See Dismissing (Navigation Back)

  1. Present sheet
navigator.navigate(type: .sheet(type: .normal)) {
  SheetView()
}
// OR
navigator.navigate(type: .sheet(type: .full)) {
  SheetView()
}
// OR
navigator.navigate(type: .sheet(type: .fixedHeight(200))) {
  SheetView()
}
  1. Present Dialog
navigator.navigate(type: .dialog) {
    ProductDetailScreen(item: item)
}

The navigation types are declared in NavType enum. See Navigation Types

NavLink

The alternative of NavigationLink. It's a wrapper of Navigator. When clicked, it will navigate to the destination view with the specified navigation type.

NavLink(destination: ProductDetailScreen(item: item)) {
    // When this view is clicked, it will trigger 
    // the navigation and show the destination view
    ProductItemView(item: item)
}

Dismissing (Navigation Back)

You can dismiss the current view:

  • Using NavLink.
DismissLink {
    Label("Back", systemImage: "chevron.backward")
            .foregroundColor(.blue)
}
  • Or using Navigator
navigator.dismiss()

Important Note: You have 4 options in dismissing the current view. for more details, see DismissType

DismissType

DismissType Defines the type of dismiss operation.

public enum DismissType {
    /// Navigate back to the previous view.
    case toPreviousView
  
    /// Navigate back to the root view (i.e. the first view added
    /// to the NavView during the initialization process).
    case toRootView
  
    /// Navigate back to a view identified by a specific ID.
    case toView(withId: String)
  
    // Dismiss current presented sheet
    case sheet(type: DismissSheetType? = nil)
  
    // Dismiss current presented dialog
    case dialog
}

You can pass your option to DismissLink or Navigator.dismiss()

DismissLink(type: .toRootView) {
    Label("Back", systemImage: "chevron.backward")
            .foregroundColor(.blue)
}

navigator.dismiss(type: .toRootView)

Navigation Bar

The navigation bar is built-in in the library. And you have the full control of hiding or showing it.

Control Nav Bar For All Views

NavView(showDefaultNavBar: false)

Control Nav Bar For a Single View

navigator.navigate(type: .push(showDefaultNavBar: false)) {
    SomeView()
}

navigator.push(showDefaultNavBar: false) {
    SomeView()
}

Note: The option you select for a single view overrides the selected option in NavView

In case you need a custom nav bar, you can disable the automatic one and implement your own one or use the built-in with your customizations

SomeView()
        .navBar(
                style: .normal,
                leadingView: {
                    SomeView()
                }
        )

Note: style parameter of type NavBarStyle supports normal and large navigation bars.

Transitions

You can customize the transition animation by providing NavTransition enum.

NavView(transition: .custom(push: .scale, pop: .scale)) {
    SomeView()
}

For more details about NavTransition, see Navigation Transition Types

Navigation Types

This enum defines the supported navigation types

public enum NavType {
    /// Regular navigation type.
    /// id: pass a custom ID to use when navigate back.
    /// addToBackStack: if false, the view won't be added to the back stack
    /// and won't be displayed when dismissing the view.
    case push(id: String? = nil, addToBackStack: Bool = true, showDefaultNavBar: Bool? = nil)
    /// Present a sheet
    case sheet(type: SheetType)
    case dialog(dismissOnTouchOutside: Bool = true)
}

Navigation Transition

NavTransition enum defines the supported transitions.

public enum NavTransition {
    /// Transitions won't be animated.
    case none

    /// The default transition if you didn't pass one.
    case `default`

    /// Use a custom transition for push & pop.
  case custom(push: AnyTransition, pop: AnyTransition)
}

Demo Project

DemoApp is an e-commerce app demonstrates the complete usage of the library.

๐Ÿ‘ Contribution

All Pull Requests (PRs) are welcome. Help us make this library better.

Changelog

Look at Changelog for release notes.

Contributors

Shaban Kamel

... Waiting for your name to be here ๐Ÿ’ช

License

Apache License, Version 2.0

click to reveal License
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.