En el vasto universo de la programación Swift, la transición de UIKit a SwiftUI marcó un cambio de paradigma fundamental: dejamos de modificar vistas manualmente para declarar estados que dictan cómo deben verse esas vistas. Para cualquier iOS developer moderno, entender la gestión de estado no es opcional; es la habilidad más crítica para sobrevivir en el ecosistema de Apple.
Si abres Xcode hoy y creas un proyecto en SwiftUI, te encontrarás inmediatamente con dos palabras clave que parecen mágicas: @State y @Binding. A primera vista, ambas parecen hacer que la UI se actualice, pero confundirlas es la fuente número uno de bugs, comportamientos erráticos y arquitecturas espagueti en aplicaciones de iOS, macOS y watchOS.
En este tutorial vamos a diseccionar la anatomía de @State y @Binding en SwiftUI. No solo aprenderás qué son, sino cómo funcionan “bajo el capó”, sus diferencias arquitectónicas y las mejores prácticas para desarrollar aplicaciones robustas y escalables.
Parte 1: El Dueño de la Verdad: ¿Qué es @State?
Para entender @State, primero debemos entender cómo funciona una Vista (View) en SwiftUI. A diferencia de UIView en UIKit (que son clases y objetos persistentes), una View en SwiftUI es una estructura (struct). Es un tipo de valor, efímero y ligero. Se crea y se destruye miles de veces.
Entonces, si la estructura se destruye y se recrea constantemente, ¿dónde se guardan los datos? Si tienes un contador en 5, y la vista se redibuja, ¿por qué no vuelve a 0?
Definición Técnica
@State es un property wrapper (envoltorio de propiedad) que le indica a SwiftUI que debe reservar memoria en el “heap” (montón) para almacenar un valor, fuera del ciclo de vida de la estructura de la vista.
Cuando marcas una propiedad con @State, estás diciendo: “Yo, esta Vista, soy la dueña absoluta de este dato. Es mi Fuente de Verdad (Source of Truth).”
El Ciclo de Vida del @State
- Inicialización: SwiftUI asigna almacenamiento para la variable.
- Modificación: Cuando cambias el valor de una variable
@State, SwiftUI detecta el cambio. - Reacción: SwiftUI invalida la vista actual y vuelve a calcular la propiedad
body. - Renderizado: La UI se actualiza para reflejar el nuevo estado.
Ejemplo Práctico en Xcode
Imagina un simple contador. Este es el ejemplo “Hola Mundo” del estado.
import SwiftUI
struct CounterView: View {
// @State declara propiedad: "Esta vista posee este entero"
// Es privado porque nadie más debe tocar la fuente de verdad directamente
@State private var count: Int = 0
var body: some View {
VStack(spacing: 20) {
Text("Contador: \(count)")
.font(.largeTitle)
.fontWeight(.bold)
HStack {
Button("Restar") {
// Modificar el estado dispara el redibujado
count -= 1
}
.buttonStyle(.bordered)
Button("Sumar") {
count += 1
}
.buttonStyle(.borderedProminent)
}
}
.padding()
}
}
Reglas de Oro para @State
- Siempre Privado: Declara tus propiedades
@Statecomoprivate. Refuerza la idea de que ese estado pertenece exclusivamente a esa vista. - Tipos Simples:
@Stateestá diseñado para tipos de valor simples:Int,String,Bool, ostructspequeños. No lo uses para objetos complejos o lógica de negocio pesada (para eso existen@StateObjectu@Observable). - Localidad: Úsalo para estados que solo importan a la interfaz de usuario local (ej: si un botón está resaltado, si un menú está desplegado, o el texto de un campo de entrada).
Parte 2: El Conector: ¿Qué es @Binding?
Si @State es el dueño de la casa, @Binding es la persona que tiene una copia de las llaves.
En una arquitectura de programación Swift limpia, solemos dividir nuestras pantallas grandes en vistas más pequeñas y reutilizables (Child Views). A menudo, estas vistas hijas necesitan controlar un valor que pertenece a la vista padre.
Aquí surge el problema: Las structs en Swift son tipos de valor. Si pasas una variable simple a la vista hija, Swift pasa una copia. Si la hija cambia su copia, el padre nunca se entera.
Definición Técnica
@Binding es un property wrapper que crea una conexión de lectura y escritura (bidireccional) entre una vista y una fuente de verdad que reside en otra parte.
No posee el dato. Simplemente es una referencia, un puntero seguro que dice: “No tengo el dato, pero tengo permiso para leerlo y modificarlo en la fuente original”.
La Metáfora del Interruptor
Imagina la instalación eléctrica de tu casa.
- @State: Es la bombilla y el cableado real en el techo (la fuente de la luz).
- @Binding: Es el interruptor en la pared. El interruptor no “tiene” luz dentro, pero controla la bombilla que está en otro lugar.
Ejemplo Práctico: Componente Reutilizable
Vamos a extraer los botones del ejemplo anterior a una vista reutilizable.
struct ControlPanel: View {
// @Binding declara: "Alguien más me pasará esto, y yo lo modificaré"
// No tiene valor inicial (no = 0), porque no es dueño del dato.
@Binding var value: Int
var body: some View {
HStack {
Button("-") {
value -= 1 // Esto modifica el @State del padre
}
.padding()
.background(Color.red.opacity(0.2))
.cornerRadius(8)
Button("+") {
value += 1 // Esto también viaja hacia arriba
}
.padding()
.background(Color.green.opacity(0.2))
.cornerRadius(8)
}
}
}
¿Cómo conectamos esto? Usando el signo de dólar ($).
struct MainParentView: View {
@State private var score = 0 // La Fuente de Verdad
var body: some View {
VStack {
Text("Puntuación: \(score)")
// Pasamos el Binding usando $
// Esto crea el "cable" entre score y value
ControlPanel(value: $score)
}
}
}
Parte 3: @State y @Binding en SwiftUI – Diferencias y Semejanzas
Para un iOS developer, saber escribir el código es solo la mitad de la batalla; entender la arquitectura es lo que te permite escalar. Vamos a comparar ambas herramientas cara a cara.
Semejanzas
- Disparadores de UI: Ambos causan que la vista se invalide y se redibuje cuando el valor cambia. Son el motor de la reactividad en SwiftUI.
- Property Wrappers: Ambos utilizan la sintaxis de
@y proyectan un valor (projectedValue) que permite enlazar con otros componentes (comoTextFieldoToggle). - Seguridad de Tipos: Ambos aprovechan el tipado fuerte de Swift. No puedes enlazar un
Binding<String>a unState<Int>.
Diferencias Clave
| Característica | @State (El Dueño) | @Binding (El Conector) |
|---|---|---|
| Propiedad del Dato | Posee y almacena el dato en memoria. | No posee nada; solo referencia a otro lugar. |
| Inicialización | Debe inicializarse con un valor (= 0, = false). | No tiene valor por defecto; se inyecta al inicializar la vista. |
| Ámbito (Scope) | Generalmente private. Local a la vista. | Generalmente internal o public. Es la API de la vista hija. |
| Origen | Es la Fuente de Verdad (Source of Truth). | Es una derivación o conexión. |
| Persistencia | Sobrevive a los redibujados de la vista. | Depende de la vida de la fuente original. |
Parte 4: Escenario Complejo: El Flujo de Datos en iOS, macOS y watchOS
Una de las bellezas de SwiftUI es que el código que escribes para gestionar @State y @Binding funciona idénticamente en un iPhone, un Mac o un Apple Watch.
Analicemos un patrón común: Un formulario de perfil de usuario. Este es un escenario donde muchos desarrolladores junior fallan al mezclar conceptos.
El Problema
Queremos una pantalla principal que muestre el nombre del usuario y un botón para editarlo. Al pulsar editar, se abre una hoja (Sheet) con un campo de texto.
La Solución Arquitectónica
- Vista Principal: Posee el dato (
@State name). - Vista de Edición: Recibe un enlace (
@Binding name) para modificarlo directamente.
import SwiftUI
// 1. La Vista Hija (Formulario)
struct EditProfileView: View {
@Binding var username: String // Enlace al dato original
@Binding var isPresented: Bool // Enlace para cerrar el modal
var body: some View {
NavigationStack {
Form {
Section("Información Pública") {
// TextField requiere un Binding, y ya tenemos uno
TextField("Nombre de usuario", text: $username)
}
}
.navigationTitle("Editar Perfil")
.toolbar {
Button("Listo") {
isPresented = false // Modifica el estado del padre para cerrar
}
}
}
}
}
// 2. La Vista Padre (Pantalla Principal)
struct ProfileDashboard: View {
@State private var currentName: String = "DevSwift2024"
@State private var showSheet: Bool = false
var body: some View {
VStack(spacing: 20) {
Image(systemName: "person.crop.circle.fill")
.font(.system(size: 100))
.foregroundColor(.blue)
Text("Hola, \(currentName)")
.font(.title2)
Button("Editar Perfil") {
showSheet = true
}
.buttonStyle(.borderedProminent)
}
.padding()
.sheet(isPresented: $showSheet) {
// Aquí pasamos los Bindings
// $currentName pasa la capacidad de escribir en currentName
// $showSheet pasa la capacidad de cerrar la hoja
EditProfileView(username: $currentName, isPresented: $showSheet)
}
}
}
Análisis del Código
Fíjate en la elegancia de este patrón. EditProfileView no necesita saber de dónde viene el nombre, ni si está guardado en una base de datos o en memoria. Solo sabe que tiene permiso para editar un String. Esto hace que EditProfileView sea altamente testeable y reutilizable.
Parte 5: Errores Comunes y Mejores Prácticas
Incluso los expertos en programación Swift cometen errores con estos wrappers. Aquí te dejo una lista de verificación para tus proyectos en Xcode.
1. Inicializar @State con valores externos
Este es el error más común.
struct BadView: View {
var initialTitle: String
@State private var title: String
init(text: String) {
self.initialTitle = text
// MAL: Esto solo funcionará la PRIMERA vez que se crea la vista.
// Si el padre cambia 'text', el @State NO se reinicializará.
_title = State(initialValue: text)
}
}
Solución: Si el valor viene de fuera y puede cambiar, ¡usa @Binding, no @State! @State es solo para almacenamiento interno inicial.
2. Olvidar el modificador private
Si no marcas tu @State como private, estás invitando a otros desarrolladores (o a ti mismo en el futuro) a intentar inyectar ese estado desde fuera mediante el inicializador miembro a miembro. Esto rompe la encapsulación y el principio de “Fuente de Verdad única”.
3. Usar @State para Modelos de Datos Complejos
Si tienes una clase User con 20 propiedades y lógica de negocio, no uses @State.
- Usa
@Statepara valores simples (UI local). - Usa
@StateObject(pre-iOS 17) o la macro@Observable(iOS 17+) para modelos de datos complejos.@Stateno está optimizado para observar cambios profundos dentro de clases.
Conclusión
Dominar @State y @Binding en SwiftUI es el primer gran paso para convertirse en un iOS developer senior. Estas dos herramientas forman la columna vertebral del flujo de datos en tus aplicaciones.
Recuerda siempre la regla de oro:
- Pregúntate: “¿Quién es el dueño de este dato?”.
- Si la respuesta es “Yo, esta vista”, usa @State.
- Si la respuesta es “Alguien más, yo solo lo edito”, usa @Binding.
Al aplicar estos conceptos correctamente en Xcode, notarás que tus aplicaciones para iOS, macOS y watchOS se vuelven más predecibles, con menos errores de sincronización y mucho más fáciles de mantener. La magia de SwiftUI reside en su simplicidad declarativa; no luches contra ella, fluye con el estado.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










