Navigation library for SwiftUI apps
NavigationKit
The NavigationKit is a library thats extends SwiftUI implementation for NavigationStack (iOS 16+ only) and adds more resources to managed the user interface.
Installation
This repository is distributed through SPM, being possible to use it in two ways:
- Xcode
In Xcode 14, go to File > Packages > Add Package Dependency...
, then paste in
https://github.com/brennobemoura/navigation-kit.git
- Package.swift
// swift-tools-version: 5.7
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]
)
],
dependencies: [
.package(url: "https://github.com/brennobemoura/navigation-kit.git", from: "1.0.0")
],
targets: [
.target(
name: "MyPackage",
dependencies: ["RequestDL"]
)
]
)
Usage
The main features available are the ones listed down below. For each one there is a problem solving solution that was developed thinking to solve the coupled SwiftUI’s View
NKNavigationStack
struct ContentView: View {
var body: some View {
NKNavigationStack {
FirstView()
}
}
}
Using the NKNavigationStack replaces the NavigationPath with NavigationAction that allows developers to manipulate in a better way the current stacked views.
⚠️ The downside of this implementation is the removal of Decode option that Apple offers to us. But you can still implement your own version of NavigationStack with NavigationPath and use the NavigationKit without the NavigationAction environment.
struct FirstView: View {
@Environment(\.navigationAction) var navigationAction
var body: some View {
Button("Push") {
// SomeModel needs to be mapped using
// navigationDestination(for:destination:)
// using SwiftUI's method.
navigationAction.append(SomeModel())
}
}
}
ViewResolver
struct FirstView: View {
@Environment(\.viewResolver) var viewResolver
var body: some View {
// SomeModel needs to be mapped using
// viewResolver(for:_:) {}.
viewResolver(SomeModel())
}
}
To map the model with the corresponding view, it’s available the viewResolver(for:_:) that needs to be specified one view before the usage.
struct ContentView: View {
var body: some View {
NKNavigationStack {
FirstView()
.viewResolver(for: SomeModel.self) {
SecondView($0)
}
}
}
}
SceneAction
struct FirstView: View {
@Environment(\.sceneAction) var sceneAction
var body: some View {
Button("Push") {
// SomeModel needs to be mapped using
// sceneAction(for:perform:) and
// the sceneActionEnabled() called in the root
// hierarchy.
sceneAction(SomeModel())
}
}
}
To map the action it’s necessary to call the sceneAction(for:perform:) method which will capture the action thrown in every place that it might be listened.
⚠️ SceneAction environment is only available when sceneActionEnabled() method is called before.
struct ContentView: View {
var body: some View {
NKNavigationStack {
FirstView()
}
.sceneAction(for: SomeModel.self) {
print("Action received: \($0)")
}
// but, sceneActionEnabled is needed before
// somewhere in the application
.sceneActionEnabled()
}
}
Suggestion: call sceneActionEnabled in App’s body property.
ViewModelConnection
The ViewModelConnection makes possible to connect a ViewModel into a View keeping the SwiftUI State sync and upright.
This implementation was design to be used inside Coordinator struct.
struct SecondCoordinator: View {
let model: SomeModel
var body: some View {
ViewModelConnection(model, SecondViewModel.init) { viewModel in
SecondView(viewModel: viewModel)
}
}
}
To managed the flow as Coordinator was meant to be, you need to specify the destination property for the ViewModel as you can work like this:
struct SecondCoordinator: View {
let model: SomeModel
var body: some View {
ViewModelConnection(model, SecondViewModel.init) { viewModel in
SecondView(viewModel: viewModel)
.onReceive(viewModel.$destination) { destination in
switch destination {
case .error(let error):
errorScene(error)
case .third(let third):
thirdScene(third)
case .none:
break
}
}
}
}
}
Inside the errorScene or thirdScene you can call the navigationAction or sceneAction to perform something.