¿Cómo funciona la navegación en el SwiftUI moderno (NavigationStack) frente al antiguo NavigationView?
NavigationView empujaba pantallas mediante estado oculto en cada link, lo que rompía el deep linking y la navegación programática; NavigationStack lo sustituyó por una ruta explícita y observable que tú controlas.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo funciona la navegación en el SwiftUI moderno (NavigationStack) frente al antiguo NavigationView?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.Cuando le preguntas a un candidato senior de iOS sobre navegación, en realidad estás midiendo algo más concreto: ¿ha vivido la era de NavigationView y sobrevivido a la migración, o solo conoce NavigationStack? Cualquiera de las dos respuestas puede estar bien, pero el candidato tiene que saber por qué Apple reemplazó la API en iOS 16 y no limitarse a recitar que ocurrió. La versión corta: NavigationView gestionaba el estado de navegación de forma implícita, atado a la identidad de cada vista, y ese estado implícito es justo lo que hacía tan doloroso el deep linking, la navegación programática y la restauración de estado.
#Cómo funcionaba NavigationView
NavigationView empujaba una pantalla nueva cada vez que un NavigationLink(destination:) dentro de él se activaba — normalmente porque el usuario lo tocaba, lo cual invertía un booleano oculto que pertenecía al propio link. Navegar programáticamente significaba recurrir a NavigationLink(destination:isActive:) o al inicializador de tag/selection, enlazando un Bool o un valor opcional a un link que quizá ni siquiera estaba visible en pantalla todavía:
struct OldStyleListView: View {@State private var showDetail = false@State private var selectedItem: Item?var body: some View {NavigationView {List(items) { item inButton(item.title) {selectedItem = itemshowDetail = true}}.background(NavigationLink(destination: DetailView(item: selectedItem),isActive: $showDetail) { EmptyView() }.hidden())}}}
Ese patrón de "NavigationLink oculto pegado al fondo" no era un accidente de un código mal escrito: era el workaround recomendado, porque NavigationView no tenía ningún concepto de pila de navegación que pudieras inspeccionar o mutar desde fuera. Además arrastraba una trampa de rendimiento muy conocida: una List llena de filas con NavigationLink(destination:) podía construir todas las vistas de destino de forma anticipada en lugar de perezosa, porque el closure de destino se evaluaba mucho antes de que el usuario tocara ninguna fila. Los equipos lo resolvían con un LazyView artesanal que retrasaba el body hasta que el link se activaba de verdad.
#Cómo funciona NavigationStack
NavigationStack invierte el modelo: el contenido de la pila es un valor explícito y observable que tú posees — un array tipado o un NavigationPath — y empujar o sacar pantallas es simplemente mutar ese valor.
enum Route: Hashable {case detail(Item)case settings}struct ModernListView: View {@State private var path: [Route] = []var body: some View {NavigationStack(path: $path) {List(items) { item inNavigationLink(item.title, value: Route.detail(item))}.navigationDestination(for: Route.self) { route inswitch route {case .detail(let item):DetailView(item: item)case .settings:SettingsView()}}.navigationTitle("Items")}}}
NavigationLink(value:) ya no sabe ni le importa qué es lo que empuja: simplemente añade un valor a la ruta cuando se toca. El modificador .navigationDestination(for:) es una tabla de búsqueda: "cuando aparezca un valor de este tipo en la ruta, construye esta vista." La navegación programática ahora es solo mutar un array: path.append(.settings) empuja, path.removeLast() saca la última pantalla, path = [] vuelve a la raíz. Como path es un estado normal, es trivial persistirlo, reproducirlo o gestionarlo desde un view model en vez de desde un @State local a la vista.
Para rutas heterogéneas que no quieres unificar en un solo enum, NavigationPath es el contenedor con borrado de tipo (type-erased) construido exactamente para eso: acepta cualquier valor Hashable, y su representación .codable permite persistir toda una pila de navegación entre lanzamientos de la app, algo que NavigationView nunca ofreció de forma soportada.
struct RootView: View {@State private var path = NavigationPath()var body: some View {NavigationStack(path: $path) {HomeView().navigationDestination(for: Item.self) { DetailView(item: $0) }.navigationDestination(for: Settings.self) { SettingsView() }}}}
Para layouts multi-columna en iPad y Mac, NavigationSplitView sustituye aquel comportamiento de NavigationView que "se convertía automáticamente en split view con ancho regular" por una API explícita de sidebar/contenido/detalle, de modo que el comportamiento responsive es algo que configuras y no algo que simplemente te pasa.
🧠 NavigationStack vs NavigationView
0/2 · 0/2 answered1. ¿Cuál es la diferencia arquitectónica clave entre NavigationStack y el antiguo NavigationView?
2. ¿Cómo empujas una pantalla nueva de forma programática con NavigationStack, dado `@State private var path: [Route] = []`?