The logo is contributed with โค๏ธ by Mahmoud Hussein
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()
}
Let's first explore the limitations of SwiftUI then explore the awesome features SwiftUINavigator provides.
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
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
- Swift 5.3+
Select File
-> Swift Packages
-> Add Package Dependency
and enter https://github.com/Open-Bytes/SwiftUINavigator
.
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.
- Import
SwiftUINavigator
.
import SwiftUINavigator
- Declare
NavView
in the root view of the app.
NavView {
HomeScreen()
}
NavView
supports transition animations and other options. See NavView
- Navigate to your destination view:
// Declare navigator
@EnvironmentObject private var navigator: Navigator
// Navigate
navigator.navigate {
SomeView()
}
For more details about
Navigator
, see Navigator
- Using
NavLink
.
NavLink(destination: SomeView()) {
// When this view is clicked, it will trigger
// the navigation and show ProductItemView
ProductItemView()
}
For more details about
Navigator
, see NavLink
- Dismiss (navigate back) to the previous view
programmatically (using
Navigator
) or using a link (usingDismissLink
).
- Using
Navigator
navigator.dismiss()
- Or using
DismissLink
.
DismissLink {
Label("Back", systemImage: "chevron.backward")
.foregroundColor(.blue)
}
For more details about dismissing, see Dismissing (Navigation Back)
navigator.presentActionSheet {
ActionSheet(
title: Text("Color"),
buttons: [
.default(Text("Red")),
.default(Text("Green")),
.default(Text("Blue")),
.cancel()
]
)
}
navigator.presentConfirmationDialog(titleKey: "Color", titleVisibility: .visible) {
Group {
Button(action: {}) {
Text("Red")
}
Button(action: {}) {
Text("Green")
}
Button(action: {}) {
Text("Blue")
}
}
}
navigator.presentAlert {
Alert(
title: Text("Alert"),
message: Text("Presented on the fly with SwiftUINavigator"),
dismissButton: .cancel())
}
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)
}
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
is the alternative of SwiftUI NavigationView implementing stack-based navigation with more control and
flexibility in handling the navigation
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
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
- 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)
- 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()
}
- Present Dialog
navigator.navigate(type: .dialog) {
ProductDetailScreen(item: item)
}
The navigation types are declared in NavType enum. See Navigation Types
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)
}
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
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)
The navigation bar is built-in in the library. And you have the full control of hiding or showing it.
NavView(showDefaultNavBar: false)
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 typeNavBarStyle
supportsnormal
andlarge
navigation bars.
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
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)
}
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)
}
DemoApp is an e-commerce app demonstrates the complete usage of the library.
All Pull Requests (PRs) are welcome. Help us make this library better.
Look at Changelog for release notes.
... Waiting for your name to be here ๐ช
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.