Take control over navigation in your SwiftUI app. Navigate without using NavigationLink.
- Present View Controllers from your SwiftUI views
- Uses the UINavigationController already present when using a NavigationView
- Smooth transitions between navigation bars when presenting
This package has 2 main components to it:
-
A Navigation class exposing the navigation methods. It's created at the start of the app and passed to SwiftUI views via environmentObject.
-
A DestinationView protocol used by SwiftUI Views that can be presented to expose their navigation bar title details.
There's a very detailed write-up on how this came to be and how it was built on Medium.
Create the Navigation object and passing it in to your root view in your SceneDelegate.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let navigation = Navigation(window: window)
// here we pass in the navigation object to our app's root view.
let rootView = UIHostingController(rootView: contentView.environmentObject(navigation))
window.rootViewController = rootView
self.window = window
self.navigation = navigation
window.makeKeyAndVisible()
}
}
Use the navigation object in your root app view to present other views.
struct ContentView: View {
@EnvironmentObject var navigation: Navigation
var body: some View {
NavigationView {
Text("Hello, world!")
.padding()
.navigationBarTitle("Example", displayMode: .large)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
navigation.pushView(SettingsView(), animated: true)
}) { Image(systemName: "gear") }
}
}
}
}
}
Have your presented view conform to DestinationView
to expose navigation title bar properties.
You can use the custom .navigationBarTitle modifier to pass in your configuration object.
struct SettingsView: View, DestinationView {
var navigationBarTitleConfiguration = NavigationBarTitleConfiguration(title: "Settings", displayMode: .inline)
var body: some View {
Text("Settings View")
.navigationBarTitle(configuration: navigationBarTitleConfiguration)
}
}
If using with a project that only has a SwiftUI lifecycle, you don't have a SceneDelegate to hook into. For this case we've created a helper WindowReader
view that will expose the view so you can create your navigation object:
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
WindowReader { window in
ExampleView()
.environmentObject(Navigation(window: window!))
}
}
}
}
Yes. Instead of calling pushView which takes a SwiftUIView, call pushViewController, which works with UIViewController's.
Yes. You can use the present(_ viewController: UIViewController) method in the navigation object.