Abstract encapsulation of the default NavigationStack with custom functionalities aimed at simplifying SwiftUI view management and navigation.
Push, present, and pop your views elegantly, helping you reduce time, complexity, and maintain code clarity.
And it's Super Lightweight!
✅ 100% SwiftUI
✅ Powerful solution for your Navigation
✅ Super easy-to-use
✅ Lightweight
✅ Supports any kind of Views
✅ Can dynamically push / present your Views in a SwiftUI
✅ Can predefine the pre-loading of your views in the app, ahead of use, in a more conventional way if needed
- iOS 16.0+, macOS 14.0+
- SwiftUI 5.0+
Swift Package Manager:
XCode
> File
> Add Package Dependency...
Enter the following URL
of the repository into the search:
https://github.com/MaximeFILIPPI/NavigationStackEX
Follow the prompts to complete the installation.
Here is an example of how to integrate and use NavigationStackEX in your SwiftUI project:
import SwiftUI
import NavigationStackEX // <- Import
@main
struct YourProject: App {
var body: some Scene {
WindowGroup {
NavigationStackEX { // <- use THIS instead of NavigationStack
ContentView()
}
}
}
}
To navigate between your views, simply add @EnvironmentObject var navigator: Navigator
Then use the navigator
functions as push
, present
, presentFullScreen
, pop
, popToRoot
, dismiss
to trigger the navigation:
import SwiftUI
import NavigationStackEX // <- Import
struct ContentView: View {
@EnvironmentObject var navigator: Navigator // <- Add this line to the view
var body: some View {
VStack {
Button("Go to Profile") {
self.goToNextScreen()
}
}
}
func goToNextScreen()
{
navigator.push(to: ProfileView()) // <- use THIS to navigate
}
}
THAT'S IT!
PUSH
Navigate to next screen
// Navigate to a SwiftUI view instance
navigator.push(to: ProfileView())
POP
Back to previous screen
// Back to the previous SwiftUI view
navigator.pop()
Back to the root of your navigation
// Back to the very first SwiftUI view
navigator.popToRoot()
Back to a specific screen in the stack
Note: You must use the parameter "identifier" when navigating to activate this functionnality (example:
navigator.push(to: ProfileView(), identifier: "profile"))
)
// Back to a specified SwiftUI view
navigator.pop(to: "profile") // <- will take you back to the profile view in your navigation stack
PRESENT
Note: Be careful the present modal way of displaying views is not stackable at the moment.
// Navigate to a SwiftUI view instance
navigator.present(ProfileView())
PRESENT FULL SCREEN
Cover opening full screen
// Navigate to a SwiftUI view instance
navigator.presentFullScreen(ProfileView())
DISMISS
Close a modal that has been present
or presentFullScreen
:
// Navigate to a destination
navigator.dismiss()
How to pass data? Just like any SwiftUI views:
// Set data using a view instance
navigator.push(to: ProfileView(name: "Max"))
BACK BUTTON
You can change the back button of the navigation bar if you wish:
import SwiftUI
import NavigationStackEX
struct TempScreenView: View {
var body: some View {
ZStack {
Color.red
}
.navigationBackButtonItem(Text("Back")) // Customize back button if needed (can be any view)
}
}
RIGHT ITEM / LEFT ITEM
or the left / right navigation bar items :
import SwiftUI
import NavigationStackEX
struct TempScreenView: View {
var body: some View {
ZStack {
Color.red
}
.navigationLeftViewItem(YourOwnCustomBackButtonView()) // <- Right navigation bar button (your own custom view)
.navigationRightViewItem(YourOwnCustomLogoView()) // <- Left navigation bar button (your own custom view)
}
}
LARGE TITLE
Change the way the title is diplayed (Large or Inline)
import SwiftUI
import NavigationStackEX
struct TempScreenView: View {
var body: some View {
ZStack {
Color.red
}
.navigationTitle("Title")
.navigationBarHidden(false)
.toolbarTitleDisplayMode(.large) // Large or Inline for better user experience (avoid using automatic)
}
}
HIDDEN TITLE
or not displayed at all and hide the navigation bar:
import SwiftUI
import NavigationStackEX
struct TempScreenView: View {
var body: some View {
ZStack {
Color.red
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
For people that prefers to keep everything tidy in one class only, you can use a more traditional approach to navigate by declaring and pre-loading the screens (SwiftUI views) that you need before using them.
import SwiftUI
import NavigationStackEX // <- Import
@main
struct YourProject: App {
@State var segues: [String: AnyView] = [:] // <- Add this line to prepare destinations
var body: some Scene {
WindowGroup {
NavigationStackEX(destinations: $segues) { // <- The NavigationEX
ContentView() // <- Your root view (first view)
}
.onAppear {
self.setupNavigation()
}
}
}
// You can setup the destinations ahead of time
// By predefining your views with associated tags
// It allows you to navigate in a more classical way by referencing tags instead of views instance
func setupNavigation()
{
// Example for a profile view in your app
self.segues["profile"] = ProfileView().any // <- add .any modifier behind your view
// Add more segues as needed
}
}
Then use the functions like this:
PUSH
// Navigate to a destination from a tag
navigator.push(to: "profile")
POP
// Back to the previous SwiftUI view
navigator.pop()
// Back to the very first SwiftUI view
navigator.popToRoot()
// Back to a specified SwiftUI view
navigator.pop(to: "profile") // <- will take you back to the profile view of your app, wherever it is in your navigation stack
PRESENT
// Navigate to a destination
navigator.present("profile")
PRESENT FULL SCREEN
// Navigate to a destination
navigator.presentFullScreen("profile")
DISMISS
Close a modal that has been present
or presentFullScreen
:
// Navigate to a destination
navigator.dismiss()
PASSING / RETRIEVING DATA
How to pass data:
// Set data using a destination (tag reference)
let data: String = "Max" // <- Can be any type of data, primivite, objects, etc...
navigator.push(to: "profile", with: data)
Get the data:
navigator.data(for: "profile") as? String // <- Can be any type of data, primivite, objects, etc...
Profile class example when retrieving data:
struct ProfileView: View {
@State var name: String = ""
var body: some View {
ZStack {
Text(name)
}
.onAppear {
loadInfos()
}
}
func loadInfos()
{
if let profileData = navigator.data(for: "profile") as? String // <- Get it here
{
name = profileData
}
}
}
SnapPager is available under the MIT license. See the LICENSE file for more details.
NavigationStackEX is developed and maintained by [Maxime FILIPPI].
If you encounter any issues or have suggestions for improvements, please open an issue.