-
Notifications
You must be signed in to change notification settings - Fork 0
Navigation Router
With this library you can finally have a SwiftUI Navigation as EASY as it should have been in the first place.
Here's a list of features Navigation Router has to offer:
- Simple and unified API
- Supports iOS, macOS, tvOS and watchOS
- Has async/await API
- Has completion handler API
- Supports iOS16
- Supports SwiftUI4
- Navigate to any view from any view
- Supports Dependency Injection (move data between views easily)
- Perfect for deep links
- Setup all your destinations in an enum
- Navigate with ONLY 1 line of code
- Supports stack, .sheet and .fullScreenCover navigation
- Create a flow for ANY type of navigation
- Built on top of NavigationStack introduced in SwiftUI4
- Type-safe navigation
- Pop to root
- Pop any number of views
- Pop to an index
- Pop to nearest root
- Pop one by one
- Built in Swift 5.7 and SwiftUI 4
- Example Xcode Project
- 100% Documented
First you want to do some setup so you can use the Navigation API effortlessly later on.
First we want to create Views that we are going to navigate to. Of course any of these Views can be used as a root.
import SwiftUI
import kindaSwiftUI
struct CroissantView: View {
var body: some View {
List {
}
.navigationTitle("π₯")
}
}
import SwiftUI
import kindaSwiftUI
struct PizzaView: View {
var body: some View {
List {
}
.navigationTitle("π")
}
}
import SwiftUI
import kindaSwiftUI
struct FruitView: View {
let title: String
var body: some View {
List {
}
.navigationTitle(title)
}
}
Note that we added the title as a String dependency to the FruitView.
With NavigationRouter you create your Destinations as enums and then navigate to them in a type-safe manner. Create an enum that adheres to the RouterDestination protocol.
enum Destination: RouterDestination {
}
Next add in a defaultView and all your other view destinations as cases. We need the defaultView later on for modal navigation setup.
enum Destination: RouterDestination {
case defaultView
case croissant
case pizza
case fruit(dependency: String)
}
Note that any destination can have only one dependency of any type. In our example the fruit has a String dependency.
Next add the var modalValue: ModalValue protocol requirement.
We give the defaultView a ModalValue index of -1 and afterwards we just use a unique Int. Feel free to just simply count up from 0.
In order to handle dependency injection we use the ModalValue(index:, dependency:) initialiser.
var modalValue: ModalValue {
switch self {
case .defaultView: return ModalValue(index: -1)
case .croissant: return ModalValue(index: 0)
case .pizza: return ModalValue(index: 1)
case .fruit(let dependency): return ModalValue(index: 2, dependency: dependency)
}
}
Next we can create the init?(modalValue: ModalValue). Add this below your modalValue property.
init?(modalValue: ModalValue) {
switch modalValue.index {
case 0: self = .croissant
case 1: self = .pizza
case 2: self = .fruit(dependency: modalValue.dependency as? String ?? "")
default: self = .defaultView
}
}
Notes:
- we need to unwrap the type of the dependency
- for the
defaultcase we usedefaultView
Finally we add in our Views for the cases created. We use an EmptyView() for the defaultView. We also pass along the dependency to the View.
var body: some View {
switch self {
case .defaultView:
EmptyView()
case .croissant:
CroissantView()
case .pizza:
PizzaView()
case .fruit(let title):
FruitView(title: title)
}
}
This is how your NavigationRouterSetup file should look like right now:
import SwiftUI
import kindaSwiftUI
enum Destination: RouterDestination {
case defaultView
case croissant
case pizza
case fruit(dependency: String)
var modalValue: ModalValue {
switch self {
case .defaultView: return ModalValue(index: -1)
case .croissant: return ModalValue(index: 0)
case .pizza: return ModalValue(index: 1)
case .fruit(let dependency): return ModalValue(index: 2, dependency: dependency)
}
}
init?(modalValue: ModalValue) {
switch modalValue.index {
case 0: self = .croissant
case 1: self = .pizza
case 2: self = .fruit(dependency: modalValue.dependency as? String ?? "")
default: self = .defaultView
}
}
var body: some View {
switch self {
case .defaultView:
EmptyView()
case .croissant:
CroissantView()
case .pizza:
PizzaView()
case .fruit(let title):
FruitView(title: title)
}
}
}
The easiest way to spin off a Navigation Router is to create a RouterStack(root:).
RouterStack<Destination>(root: .croissant)
Your ContentView will now look like this:
import SwiftUI
import kindaSwiftUI
struct ContentView: View {
var body: some View {
RouterStack<Destination>(root: .croissant)
}
}
From now on you can access the Router as an @EnvironmentObject in any of your Views.
@EnvironmentObject private var router: Router<Destination>
Note that we provide the Destination that we created earlier for the RouterDestination of the Router.
Now it's time to learn about the amazing NavigationRouter API.
We are talking about pushing a View when we navigate from one View to another in a stack like manner.
To push the PizzaView from the CroissantView all you have to do is use the push(_:) function on the router.
router.push(.pizza)
Your CroissantView will look like this:
struct CroissantView: View {
@EnvironmentObject private var router: Router<Destination>
var body: some View {
List {
Button("Push π") {
router.push(.pizza)
}
}
.navigationTitle("π₯")
}
}