certmundo.
es‑mx

6 min de lectura

¿Cómo manejar el estado y la navegación en una app iOS?

El estado en SwiftUI es la información que tu app recuerda en cada momento, y la navegación es el mecanismo que lleva al usuario de una pantalla a otra.

¿Qué es el estado en una app iOS?

El estado es cualquier dato que determina qué se muestra en pantalla. Cuando el estado cambia, SwiftUI redibuja la interfaz automáticamente.

Por ejemplo, si el usuario escribe su nombre o selecciona un producto de Liverpool, esos valores son estado. Tu app necesita recordarlos para mostrarlos en otra pantalla.


@State: la variable que SwiftUI vigila

@State es un property wrapper que le dice a SwiftUI: "cuando este valor cambie, redibuja esta vista".

Solo úsalo para datos locales a una vista, como un contador, un texto ingresado o un interruptor activado.

Estructura básica

@State private var nombre: String = ""
@State private var precio: Double = 0.0
@State private var esMiembro: Bool = false

Ejemplo 1 — Contador de artículos en carrito

Imagina una app sencilla para una tienda como Mercado Libre:

struct CarritoView: View {
    @State private var cantidad: Int = 0

    var body: some View {
        VStack(spacing: 20) {
            Text("Artículos en carrito: \(cantidad)")
                .font(.title2)

            HStack(spacing: 16) {
                Button("−") {
                    if cantidad > 0 { cantidad -= 1 }
                }
                .buttonStyle(.bordered)

                Button("+") {
                    cantidad += 1
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .padding()
    }
}

Cada vez que el usuario toca "+" o "−", cantidad cambia y SwiftUI redibuja Text con el nuevo número. No necesitas código extra para actualizar la interfaz.


@Binding: compartir estado entre vistas

@Binding permite que una vista hija lea y modifique el estado que vive en la vista padre.

No crea datos nuevos. Solo apunta a los datos que ya existen en otra vista.

Estructura básica

// Vista padre — tiene @State
@State private var estaActivo: Bool = false

// Vista hija — recibe @Binding
struct MiToggle: View {
    @Binding var estaActivo: Bool
    ...
}

// Llamada con el signo $
MiToggle(estaActivo: $estaActivo)

El signo $ convierte una variable @State en un Binding. Sin él, solo pasas el valor actual, no la conexión en dos direcciones.

Ejemplo 2 — Formulario de registro con vista separada

Supón que tienes una app de puntos para socios de FEMSA. El formulario está dividido en dos vistas:

struct CampoNombre: View {
    @Binding var nombre: String

    var body: some View {
        TextField("Escribe tu nombre", text: $nombre)
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal)
    }
}

struct RegistroView: View {
    @State private var nombreSocio: String = ""

    var body: some View {
        VStack(spacing: 16) {
            Text("Registro de socio FEMSA")
                .font(.headline)

            CampoNombre(nombre: $nombreSocio)

            Text("Hola, \(nombreSocio.isEmpty ? "nuevo socio" : nombreSocio)")
                .foregroundStyle(.secondary)
        }
        .padding()
    }
}

CampoNombre no necesita saber nada sobre RegistroView. Solo recibe el binding y lo usa. Cuando el usuario escribe, nombreSocio en RegistroView se actualiza en tiempo real.


NavigationStack: conectar pantallas

NavigationStack es el contenedor que habilita la navegación entre vistas usando una pila (stack). La pantalla nueva se apila sobre la anterior y el usuario puede regresar con el botón atrás.

Estructura básica

NavigationStack {
    // Vista raíz
    NavigationLink("Ir a detalles", destination: DetalleView())
}

NavigationLink es el botón que empuja una nueva vista a la pila. El parámetro destination recibe la vista a la que quieres navegar.

Ejemplo 3 — App de dos pantallas: lista de productos Bimbo

Esta app muestra una lista de productos y al tocar uno abre el detalle con su precio.

struct Producto: Identifiable {
    let id = UUID()
    let nombre: String
    let precio: Double
}

let productosBimbo: [Producto] = [
    Producto(nombre: "Pan Blanco", precio: 42.5),
    Producto(nombre: "Marinela Gansito", precio: 18.5),
    Producto(nombre: "Tía Rosa Polvorón", precio: 29.0)
]

struct ListaProductosView: View {
    var body: some View {
        NavigationStack {
            List(productosBimbo) { producto in
                NavigationLink(destination: DetalleProductoView(producto: producto)) {
                    Text(producto.nombre)
                }
            }
            .navigationTitle("Productos Bimbo")
        }
    }
}

struct DetalleProductoView: View {
    let producto: Producto

    var body: some View {
        VStack(spacing: 12) {
            Text(producto.nombre)
                .font(.largeTitle)
                .bold()

            Text("Precio: $\(String(format: "%.0f", producto.precio))")
                .font(.title3)
                .foregroundStyle(.secondary)

            Spacer()
        }
        .padding()
        .navigationTitle("Detalle")
        .navigationBarTitleDisplayMode(.inline)
    }
}

Cuando el usuario toca "Pan Blanco", NavigationStack empuja DetalleProductoView con ese producto. El botón atrás aparece automáticamente en la barra de navegación.


Estado + navegación juntos

Puedes combinar @State y NavigationStack para controlar la navegación desde código, no solo desde botones.

Ejemplo 4 — Navegación programática con navigationDestination

struct BuscadorView: View {
    @State private var busqueda: String = ""
    @State private var irAResultados: Bool = false

    var body: some View {
        NavigationStack {
            VStack(spacing: 20) {
                TextField("Busca un producto", text: $busqueda)
                    .textFieldStyle(.roundedBorder)
                    .padding(.horizontal)

                Button("Buscar") {
                    irAResultados = true
                }
                .buttonStyle(.borderedProminent)
                .disabled(busqueda.isEmpty)
            }
            .navigationTitle("Buscador")
            .navigationDestination(isPresented: $irAResultados) {
                ResultadosView(termino: busqueda)
            }
        }
    }
}

struct ResultadosView: View {
    let termino: String

    var body: some View {
        Text("Resultados para: \"\(termino)\"")
            .font(.title2)
            .padding()
            .navigationTitle("Resultados")
    }
}

.navigationDestination(isPresented:) navega cuando irAResultados se vuelve true. Así controlas la navegación desde una acción como tocar "Buscar", no solo desde un NavigationLink visible.


Errores comunes

Error 1 — Usar @State para datos compartidos entre vistas no relacionadas. @State es solo para datos locales de una vista. Si varias vistas necesitan el mismo dato, usa @Binding o, en apps más complejas, @StateObject con una clase ObservableObject.

Error 2 — Olvidar el signo $ al pasar un Binding. Escribir CampoNombre(nombre: nombreSocio) pasa una copia del valor, no la conexión. La vista hija no podrá actualizar el dato original. Siempre usa $nombreSocio cuando el parámetro espera un Binding.

Error 3 — Colocar NavigationStack dentro de otra vista que ya tiene NavigationStack. Anidar dos NavigationStack produce comportamientos inesperados: barras de navegación duplicadas o botones atrás que no funcionan. Define un solo NavigationStack en la vista raíz de cada flujo.

Error 4 — Modificar variables @State fuera del hilo principal. Si obtienes datos de una API (por ejemplo, precios del SAT o inventario de Liverpool), actualiza las variables @State siempre en el hilo principal con await MainActor.run { } o marcando la función con @MainActor. De lo contrario, la interfaz puede no actualizarse correctamente.


Tabla de referencia rápida

Herramienta ¿Dónde vive el dato? ¿Para qué sirve?
@State En la vista actual Variables locales que cambian con interacción
@Binding En la vista padre Compartir y modificar estado desde una vista hija
NavigationStack Vista contenedora Habilita navegación con pila entre pantallas
NavigationLink Dentro del stack Botón que empuja una nueva vista
.navigationDestination Modificador de vista Navegación programática desde código

Puntos clave

  • @State almacena datos locales de una vista. Cuando cambia, SwiftUI redibuja body automáticamente.
  • @Binding conecta una vista hija con el @State de su padre. El signo $ es obligatorio para crear esa conexión.
  • NavigationStack es el contenedor que habilita la navegación por pila. Defínelo una sola vez por flujo.
  • NavigationLink y .navigationDestination son las dos formas de navegar: la primera desde un elemento visible, la segunda desde una condición en el código.
  • Combinar @State con .navigationDestination(isPresented:) te permite navegar cuando ocurre una acción, no solo cuando el usuario toca un botón específico.

Puntos clave

  • `@State` almacena datos locales de una vista. Cuando su valor cambia, SwiftUI redibuja `body` automáticamente sin código adicional.
  • `@Binding` permite que una vista hija lea y modifique el `@State` de su vista padre. El signo `$` es obligatorio al pasar el binding: sin él, solo pasas una copia del valor.
  • `NavigationStack` es el contenedor que habilita la navegación por pila entre pantallas. Define un solo `NavigationStack` por flujo para evitar barras duplicadas.
  • `NavigationLink` navega al tocar un elemento visible. `.navigationDestination(isPresented:)` navega cuando una variable `@State` de tipo `Bool` se vuelve `true`, útil para acciones como tocar un botón "Buscar".
  • No uses `@State` para datos que deben compartirse entre varias vistas no relacionadas. Para esos casos, la solución correcta es `@Binding` o un `ObservableObject`.

Comparte esta lección:

¿Cómo manejar el estado y la navegación en una app iOS? | Swift para iOS Básico: Programa tu primera app para iPhone | Certmundo