Coordinadores de Navegación en SwiftUI: Implementando un TabBar con Stacks Independientes
La navegación en SwiftUI puede volverse compleja a medida que tu aplicación crece. Usar un Coordinador de Navegación nos permite gestionar mejor las rutas y mantener el código organizado. En este artículo, exploraremos cómo implementar un coordinador de navegación robusto para una aplicación de iOS que navega con una barra de pestañas, y aprende a mantener las vistas con stacks independientes que no pierdan su navegación previamente realizada.
¿Por qué usar un Coordinador de Navegación?
Existen varias razones por las cuales es buena práctica usar un Coordinador de Navegación. Los principales beneficios son:
• Mantener la navegación modular y organizada. • Preservar el estado de navegación de cada pantalla. • Separar la lógica de navegación del UI.
Paso 1: Definir las Rutas
Creamos un enum para representar las diferentes pantallas de la app:
enum Routes: Hashable {
case Home
case Detail
case Settings
case Profile
case Notifications
}
Paso 2: Crear el Coordinador de Navegación
El coordinador mantendrá las rutas y creará las navegaciones:
import SwiftUI
class NavigationCoordinator: ObservableObject {
@Published var path = NavigationPath()
func push(_ route: Routes) {
path.append(route)
}
func goToRoot() {
path.removeLast(path.count)
}
func pop() {
if !path.isEmpty {
path.removeLast()
}
}
@ViewBuilder
func build(route: Routes) -> some View {
switch route {
case .Home:
HomeView()
case .Detail:
DetailView()
case .Settings:
SettingsView()
case .Profile:
ProfileView()
case .Notifications:
NotificationsView()
}
}
}
Paso 3: Crear la Vista de TabBar con Coordinadores
Cada pantalla tendrá su propio coordinador independiente:
struct MainTabBarView: View {
@StateObject private var homeCoordinator = NavigationCoordinator()
@StateObject private var settingsCoordinator = NavigationCoordinator()
@StateObject private var profileCoordinator = NavigationCoordinator()
@StateObject private var notificationsCoordinator = NavigationCoordinator()
var body: some View {
TabView {
NavigationStack(path: $homeCoordinator.path) {
homeCoordinator.build(route: .Home)
.navigationDestination(for: Routes.self) { route in
homeCoordinator.build(route: route)
}
}
.environmentObject(homeCoordinator)
.tabItem {
Label("Home", systemImage: "house")
}
NavigationStack(path: $settingsCoordinator.path) {
settingsCoordinator.build(route: .Settings)
.navigationDestination(for: Routes.self) { route in
settingsCoordinator.build(route: route)
}
}
.environmentObject(settingsCoordinator)
.tabItem {
Label("Settings", systemImage: "gearshape")
}
NavigationStack(path: $profileCoordinator.path) {
profileCoordinator.build(route: .Profile)
.navigationDestination(for: Routes.self) { route in
profileCoordinator.build(route: route)
}
}
.environmentObject(profileCoordinator)
.tabItem {
Label("Profile", systemImage: "person")
}
NavigationStack(path: $notificationsCoordinator.path) {
notificationsCoordinator.build(route: .Notifications)
.navigationDestination(for: Routes.self) { route in
notificationsCoordinator.build(route: route)
}
}
.environmentObject(notificationsCoordinator)
.tabItem {
Label("Notifications", systemImage: "bell")
}
}
}
}
TabView {
NavigationStack(path: $homeCoordinator.path) {
homeCoordinator.build(route: .Home)
.navigationDestination(for: Routes.self) { route in
homeCoordinator.build(route: route)
}
}
.environmentObject(homeCoordinator)
.tabItem {
Label("Home", systemImage: "house")
}
NavigationStack(path: $settingsCoordinator.path) {
settingsCoordinator.build(route: .Settings)
.navigationDestination(for: Routes.self) { route in
settingsCoordinator.build(route: route)
}
}
.environmentObject(settingsCoordinator)
.tabItem {
Label("Settings", systemImage: "gearshape")
}
NavigationStack(path: $profileCoordinator.path) {
profileCoordinator.build(route: .Profile)
.navigationDestination(for: Routes.self) { route in
profileCoordinator.build(route: route)
}
}
.environmentObject(profileCoordinator)
.tabItem {
Label("Profile", systemImage: "person")
}
NavigationStack(path: $notificationsCoordinator.path) {
notificationsCoordinator.build(route: .Notifications)
.navigationDestination(for: Routes.self) { route in
notificationsCoordinator.build(route: route)
}
}
.environmentObject(notificationsCoordinator)
.tabItem {
Label("Notifications", systemImage: "bell")
}
}
Paso 4: Definir las Vistas
Estas son las vistas que se accederán en cada pantalla:
struct HomeView: View {
@EnvironmentObject var coordinator: NavigationCoordinator
var body: some View {
VStack {
Text("Home")
Button("Go to Detail") {
coordinator.push(.Detail)
}
}
}
}
struct DetailView: View {
@EnvironmentObject var coordinator: NavigationCoordinator
var body: some View {
VStack {
Text("Detail")
Button("Go to Settings") {
coordinator.push(.Settings)
}
}
}
}
struct SettingsView: View {
@EnvironmentObject var coordinator: NavigationCoordinator
var body: some View {
VStack {
Text("Settings")
Button("Go to Profile") {
coordinator.push(.Profile)
}
}
}
}
struct ProfileView: View {
@EnvironmentObject var coordinator: NavigationCoordinator
var body: some View {
VStack {
Text("Profile")
Button("Go to Notifications") {
coordinator.push(.Notifications)
}
}
}
}
struct NotificationsView: View {
@EnvironmentObject var coordinator: NavigationCoordinator
var body: some View {
VStack {
Text("Notifications")
Button("Go to Home") {
coordinator.push(.Home)
}
}
}
}
Paso 5: Usar el Coordinador en la App
Finalmente, usamos el coordinador principal en la aplicación principal:
@main
struct NavigationApp: App {
var body: some Scene {
WindowGroup {
MainTabBarView()
}
}
}
Conclusión
Listo, de esta manera podemos ver que la navegación para una aplicación, que te permitirá fácil una cadena, que te permitirá organizar mejor el proyecto complejo.