NavigationStack maneja una pila de vistas. NavigationLink es un componente que crea un botón con cierta etiqueta, que el usuario puede presionar para agregar una vista en la pila.
-
init(_:destination:).titleKeyes el texto de la etiqueta.destinationes la vista a presentar. -
init(_:value:).titleKeyes el texto de la etiqueta.valuees un valor opcional de tipoHashableasociado a una vista a presentar.nildesactiva el enlace.
struct NextView: View {
var body: some View {
ZStack {
Color.red.opacity(0.7)
Text("Vista #2")
}
.navigationTitle("Segunda vista")
.navigationSubtitle("Este es el destino de la primera")
}
}
struct ContentView: View {
var body: some View {
NavigationStack {
ZStack {
Color.blue.opacity(0.7)
Text("Vista #1")
}
.navigationTitle("Primera vista")
.navigationSubtitle("Desde acá puedo navegar")
.toolbar {
NavigationLink(destination: {
NextView()
}) {
Image(systemName: "paperplane")
}
}
}
}
}
Notar que al presionar el botón del toolbar se efectua la transición. Sin embargo, desde iOS 26 ya no aparece el título de la vista anterior al lado del botón de retroceso.
Creación de un botón de retroceso personalizado
Para empezar, se debe ocultar el botón de retroceso por defecto de la barra de navegación con navigationBarBackButtonHidden(_:).
struct NextView: View {
var body: some View {
ZStack {
Color.red.opacity(0.7)
Text("Vista #2")
}
.navigationTitle("Segunda vista")
.navigationSubtitle("Este es el destino de la primera")
.navigationBarBackButtonHidden()
}
}
Luego, se puede eliminar la vista del stack de navegación por medio de las siguientes variables de ambiente:
-
dismiss. Acción de tipoDismissActionque cierra la vista presentada. La acción se puede ejecutar como un closure. -
isPresented. Variable de ambiente que indica que la vista se está presentando.
struct NextView: View {
@Environment(\.dismiss) private var dismiss: DismissAction
var body: some View {
ZStack {
Color.red.opacity(0.7)
Text("Vista #2")
}
.navigationTitle("Segunda vista")
.navigationSubtitle("Este es el destino de la primera")
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Go back", systemImage: "chevron.backward.circle") {
dismiss()
}
.tint(.red)
}
}
}
}
Patrón maestro-detalle
En el modo "apilado" del patrón maestro-detalle, se suele tener una lista "maestra" con unos títulos de una colección y, al pulsar alguna de las filas, se navega hacia el "detalle" mostrando la información adicional del elemento.
Esto se puede implementar en SwiftUI con un List, donde cada elemento es un NavigationLink, que generalmente tiene una etiqueta simple (e.g. Text o Label), que tiene como destino la vista detalle que recibe el elemento de la lista seleccionado.
struct DetailView: View {
let person: Person
var body: some View {
VStack {
Text("Esta es la vista detalle para el siguiente personaje:")
Text("\(person.name) \(person.lastname)")
}
.navigationTitle("Detalle")
.navigationBarTitleDisplayMode(.inline)
}
}
struct ContentView: View {
@State private var searchText: String = ""
private var filteredPeople: [Person] {
if searchText.isEmpty {
people
} else {
people.filter {
$0.name.localizedStandardContains(searchText) }
}
}
var body: some View {
NavigationStack {
List(filteredPeople) { person in
NavigationLink {
DetailView(person: person)
} label: {
//Text("\(person.name) \(person.lastname)")
Label("\(person.name) \(person.lastname)", systemImage: "person")
}
}
.navigationTitle("Personas")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic), prompt: "¿A quién busca?")
}
}
}
Manipulando el stack de navegación
Se puede cambiar manualmente la pila de vistas de navegación así:
- Al crear un
NavigationLinkse debe usar el constructorinit(_:value:)para señalar cuál es el identificadorHashableasociado a la vista destino deseada. Se debe crear un tipo de dato diferente por cada destino. - Se debe modificar el
NavigationStackconnavigationDestination(for:destination:)que asocia el tipo de dato de un identificador (data) a una vista de destino (destination). Se pueden suscribir variosnavigationDestinationa unNavigationStacky entre más alto aparezca uno de ellos en la jerarquía, más prioridad tendrá. Lo anterior implica que si se usan varios valores de un mismo tipo para efectuar una transición, deben ser procesados en el mismonavigationDestination. - Al ejecutar una transición, presionando el
NavigationLink,NavigationStackagrega elvaluea unNavigationPath, que debe ser unBinding(i.e.@Stateo@Published).
NavigationPath incluye las siguientes propiedades para manejar los valores:
isEmptycount-
append(_:): Agrega un nuevovaluealNavigationPath. -
removeLast(_:): Elimina los últimoskvalores delNavigationPath
// ⚠️ Notar que Person debe ser Hashable para poder ser usado
// dentro del NavigationPath
struct Person: Identifiable, Hashable {
let id = UUID()
let name: String
let lastname: String
}
// ⚠️ Esta es una vista destino dummy
struct ContactsView: View {
// ⚠️ Aquí tengo una referencia al navigationPath
// del NavigationStack
@Binding var navigationPath: NavigationPath
var body: some View {
ZStack {
Color.yellow.opacity(0.7)
Text("Aquí muestro a mis contactos")
}
// ⚠️ Oculto el BarBackButton
.navigationBarBackButtonHidden()
.toolbar {
// ⚠️ Agrego un botón de cerrar artesanal
ToolbarItem(placement: .topBarLeading) {
Button("Cerrar", systemImage: "x.circle") {
// ⚠️ .count muestra cuántos elementos hay
print(navigationPath.count)
if !navigationPath.isEmpty {
// ⚠️ .removeLast() hace "pop" del stack
navigationPath.removeLast()
}
}
}
}
}
}
struct DetailView: View {
let person: Person
var body: some View {
VStack {
Text("Esta es la vista detalle para el siguiente personaje:")
Text("\(person.name) \(person.lastname)")
}
.navigationTitle("Detalle")
.navigationBarTitleDisplayMode(.inline)
}
}
struct ContentView: View {
@State private var searchText: String = ""
// ⚠️ Se crea un NavigationPath como Binding
@State private var navigationPath = NavigationPath()
private var filteredPeople: [Person] {
if searchText.isEmpty {
people
} else {
people.filter {
$0.name.localizedStandardContains(searchText) }
}
}
var body: some View {
// ⚠️ Se debe pasar el navigationPath como Binding al
// NavigationStack
NavigationStack(path: $navigationPath) {
List(filteredPeople) { person in
NavigationLink(value: person) {
Label("\(person.name) \(person.lastname)", systemImage: "person")
}
}
.navigationDestination(for: Person.self, destination: { person in
// ⚠️ Uno de los tipos de valores que se puede procesar
// es Person, que debe ser Hashable.
// Una vez se recibe un Person, se pasa como argumento a
// DetailView
DetailView(person: person)
})
.navigationDestination(for: String.self, destination: { destinationId in
// ⚠️ Otro de los tipos de valores que se puede procesar
// es String. Si el String recibido es "Contactos",
// entonces devuelvo la vista ContactsView(). De lo
// contrario no devuelvo nada
if destinationId == "Contactos" {
// ⚠️ Mando el navigationPath al ContactsView
ContactsView(navigationPath: $navigationPath)
}
})
.navigationTitle("Personas")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic), prompt: "¿A quién busca?")
.toolbar {
// ⚠️ En el toolbar voy a poner un NavigationLink que agrega el valor "Contactos" al path
NavigationLink(value: "Contactos") {
Label("Contactos", systemImage: "person")
}
}
}
}
}









Top comments (0)