SwiftUI #25: Estado (@State)
Source: Dev.to
@State
@State es un property‑wrapper que envuelve un valor de tipo SomeType en una estructura State y notifica al sistema cuando ese valor cambia, de modo que la vista se actualice automáticamente.
Nota: La propiedad marcada con
@Statedebería serprivateporque representa el estado interno de la vista y no debe ser modificada por sus clientes.
struct ContentView: View {
@State private var title: String = "Título inicial"
var body: some View {
VStack {
Text(title)
Button {
title = "Otro título"
} label: {
Text("Modificar")
}
}
}
}
En el ejemplo anterior, cuando title cambia, @State notifica el cambio al sistema y el contenido de body se vuelve a renderizar.
Binding
Binding es una referencia a un valor almacenado por un @State.
Para que una vista pueda modificar internamente el valor almacenado por un @State, se le pasa una referencia al State mediante su projected value, accesible añadiendo el prefijo $ al nombre de la variable.
struct ContentView: View {
@State private var tituloFinal: String = "Título inicial"
@State private var title: String = ""
var body: some View {
VStack {
Text(tituloFinal)
.padding(10)
TextField("Ingrese título", text: $title)
Button {
tituloFinal = title
} label: {
Text("Actualizar título")
}
}
}
}

Subvista que recibe un Binding
Cuando una vista necesita modificar el valor envuelto por un @State externo, se declara una variable de instancia de tipo Binding usando el property‑wrapper @Binding.
struct ContentSubview: View {
var title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
.padding(10)
TextField("Ingrese título", text: $titleInput)
}
}
}
Uso de la subvista desde la vista principal
struct ContentView: View {
@State private var tituloFinal: String = "Título inicial"
@State private var title: String = ""
var body: some View {
VStack {
ContentSubview(title: tituloFinal, titleInput: $title)
Button {
tituloFinal = title
} label: {
Text("Actualizar título")
}
}
}
}
Importante: Una propiedad
@Bindingsiempre recibe su valor de una propiedad@State, por lo que no es necesario asignarle un valor por defecto.
State y Binding como estructuras
Swift permite acceder a la estructura subyacente del property‑wrapper usando un guión bajo (_) como prefijo del nombre de la variable.
Tabla de propiedades
| Propiedad | Descripción |
|---|---|
wrappedValue | El valor manejado por @State. Se accede directamente mediante el nombre de la propiedad (sin _). |
projectedValue | Un Binding que actúa como referencia a State. Se accede mediante $ (equivalente a _nombre.projectedValue). |
struct ContentSubview: View {
var title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
.padding(10)
TextField("Ingrese título", text: _titleInput.projectedValue)
.textFieldStyle(.roundedBorder)
}
}
}
struct ContentView: View {
@State private var tituloFinal: String = "Título inicial"
@State private var title: String = ""
var body: some View {
VStack {
// Acceso a la estructura subyacente
ContentSubview(
title: _tituloFinal.wrappedValue,
titleInput: _title.projectedValue
)
Button {
_tituloFinal.wrappedValue = _title.wrappedValue
} label: {
Text("Actualizar título")
}
}
}
}
Binding manual
Aunque normalmente se usa Binding junto con @State, también es posible crear un Binding de forma manual mediante el inicializador init(get:set:), que recibe dos closures: uno para obtener el valor y otro para establecerlo. Esto resulta útil, por ejemplo, para previsualizar un componente que espera recibir un Binding.
struct ContentSubview: View {
var title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
.padding(10)
TextField("Ingrese título", text: _titleInput.projectedValue)
.textFieldStyle(.roundedBorder)
}
}
}
Preview con Binding manual
#Preview("Manual binding") {
var someTitle = ""
// Binding manual que opera sobre `someTitle`
let titleBinding = Binding(
get: { someTitle },
set: { newValue in
someTitle = newValue
}
)
ContentSubview(title: "Vista previa", titleInput: titleBinding)
}
Uso de Binding en vistas
struct ContentSubview: View {
let title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
TextField("Título", text: $titleInput)
}
}
}
Creando un Binding manualmente
struct ContentView: View {
@State private var title = ""
var body: some View {
ContentSubview(title: "Título inicial", titleInput: $title)
}
}
#Preview("Manual binding") {
@State var title = ""
ContentSubview(title: "Título inicial", titleInput: $title)
}
Nota: En la vista previa (
#Preview) no se muestra el botón de pantalla completa, por lo que los controles Enter fullscreen mode y Exit fullscreen mode se omiten.
Usando un Binding constante
A veces conviene pasar un valor inmutable al Binding. En este caso se usa el método estático .constant(_:):
#Preview("Binding constante") {
ContentSubview(title: "Título inicial", titleInput: .constant("Algún título"))
}
Creando State desde #Preview
A partir de iOS 17/macOS 14, el macro @Previewable permite declarar un @State directamente dentro de un bloque #Preview, simplificando la escritura de vistas de prueba.
#Preview("@Previewable") {
@Previewable @State var title = ""
ContentSubview(title: "Título inicial", titleInput: $title)
}
Consejo:
@Previewablesimplifica la gestión del estado en vistas de prueba, ya que el estado se actualiza en tiempo real mientras editas el código.