🔐 @State representa el estado privado interno de una vista en SwiftUI. Su valor inicial solo se aplica cuando la vista establece su identidad por primera vez en la jerarquía. Después, SwiftUI ignora completamente cualquier nuevo valor que intentes pasarle.
🧬 La clave está en entender la identidad de las vistas. SwiftUI adopta una estrategia de inicialización única: cuando la vista se carga por primera vez, crea el almacenamiento interno (State<T>). En actualizaciones posteriores, aunque el padre llame de nuevo al init de la hija con nuevos valores, SwiftUI detecta que ya existe almacenamiento para esa identidad y lo reutiliza, descartando los parámetros nuevos.
// ❌ Esto NO funciona como esperas
struct ParentView: View {
@State private var userName = "Ana"
var body: some View {
VStack {
ChildView(name: userName)
Button("Cambiar nombre") {
userName = "Carlos" // ChildView NO se actualiza
}
}
}
}
struct ChildView: View {
@State var name: String // Se queda con el valor inicial
var body: some View {
Text("Hola, \(name)")
}
}
💡 Piensa en @State como una caja cerrada que solo acepta un valor inicialal y después sólo se puede cambiar internamente desde la vista. En todas la inicializaciones posteriores, rechaza el valor inicial porque ya tiene una valor. Esto garantiza continuidad del estado y evita que, por ejemplo, el texto que un usuario está escribiendo se sobrescriba accidentalmente.
🎯 La semántica de @State es clara: es memoria exclusiva de la vista que la declara. Por eso Apple recomienda marcarlo como private. No está diseñado para ser modificado desde fuera, sino para gestionar estado local como el estado de resaltado de un botón o el texto temporal de un campo de entrada.
// ✅ Uso correcto de @State: estado privado interno
struct ToggleButton: View {
@State private var isPressed = false // Solo esta vista lo modifica
let action: () -> Void
var body: some View {
Button(action: action) {
Text("Pulsar")
.scaleEffect(isPressed ? 0.95 : 1.0)
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
}
}
🔄 Si necesitas comunicación bidireccional entre padre e hija, @Binding es la respuesta. Mientras que @State otorga propiedad completa del dato a la vista, @Binding crea una referencia de lectura/escritura sin propiedad. El padre mantiene la fuente de verdad con @State y pasa un binding usando el prefijo $.
// ✅ Solución correcta: usar @Binding
struct ParentView: View {
@State private var userName = "Ana"
var body: some View {
VStack {
ChildView(name: $userName) // Pasa el binding con $
Button("Cambiar nombre") {
userName = "Carlos" // Ahora SÍ se actualiza
}
}
}
}
struct ChildView: View {
@Binding var name: String // Referencia sin propiedad
var body: some View {
TextField("Nombre", text: $name)
}
}
⚙️ Para datos de solo lectura que la vista hija simplemente muestra, usa una propiedad let estándar. Solo recurre a @State cuando el dato sea estrictamente interno a la vista hija y no necesite sincronización con el padre.
// ✅ Para datos de solo lectura: usa let
struct ParentView: View {
@State private var userName = "Ana"
var body: some View {
VStack {
DisplayView(name: userName) // Pasa el valor directamente
Button("Cambiar nombre") {
userName = "Carlos" // DisplayView se actualiza correctamente
}
}
}
}
struct DisplayView: View {
let name: String // Propiedad inmutable
var body: some View {
Text("Usuario actual: \(name)")
}
}
⚠️ Si intentas forzar la actualización cambiando la identidad explícita con .id(valor), SwiftUI destruirá y recreará la vista completa, aplicando la nueva inicialización. Aunque funciona, tiene costes de rendimiento y pierdes otros estados internos de la vista.
🆕 Desde iOS 17, @State también sirve para tipos de referencia que se conforman a Observable, reemplazando a @StateObject. Cuidado: a diferencia de @StateObject, @State no tiene inicialización lazy (que se postpone hasta que es necesario, en lugar de hacerse en el init). Crear objetos grandes directamente en el init o como valores por defecto hará que se creen en cada redibujado, aunque se descarten inmediatamente.
// ❌ Evita esto con @State: se crea en cada redibujado
struct ContentView: View {
@State private var viewModel = HeavyViewModel() // Con mucho coste
var body: some View {
Text(viewModel.data)
}
}
// ✅ Mejor: inicialización lazy o en onAppear
struct ContentView: View {
@State private var viewModel: HeavyViewModel?
var body: some View {
Text(viewModel?.data ?? "Cargando...")
.onAppear {
if viewModel == nil {
viewModel = HeavyViewModel()
}
}
}
}
👨💻 Dominar la diferencia entre @State, @Binding y propiedades regulares es fundamental para estructurar correctamente el flujo de datos en SwiftUI. ¿Ya aplicas estas prácticas en tus proyectos?


Deja un comentario