El paradigma declarativo no solo se trata de cómo organizar las vistas, sino de que cada vez que cambia el estado de la aplicación, las vistas deben actualizarse y reflejarlo.
@State
El "property-wrapper" @State envuelve un valor de tipo SomeType en una estructura de tipo State<SomeType> y notifica al sistema cuando ese valor cambia, para que la vista se actualice automáticamente.
La propiedad marcada con @State debería ser private puesto que representa el estado de esa vista y no debería ser modificado 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 luego el contenido de body es actualizado.
Binding
Un 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 necesita pasarle una referencia al State por medio de su projectedValue, que puede ser accedido agregando 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")
}
}
}
}
Por otro lado, a la hora de construir una vista que modifique el valor envuelto por un State externo, se debe almacenar la referencia con 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)
}
}
}
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")
}
}
}
}
Una propiedad @Binding siempre recibe su valor de una propiedad @State, así que no es necesario asignar un valor por defecto.
State y Binding como estructura
Swift permite acceder a la estructura del "property-wrapper" usando un guión bajo como prefijo del nombre de la variable. Por ejemplo, si se marca una variable nombre con @State, se puede acceder a la estructura asociada con _nombre. A continuación se listan las propiedades de esta estructura:
-
wrappedValue: El valor manejado por@State(referenciado por@Binding). Se accede a este valor usando directamente el nombre de la propiedad (sin_) -
projectedValue: UnBindingque funciona como referencia haciaState. Se accede a él a través de$en lugar de_.
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 {
ContentSubview(title: _tituloFinal.wrappedValue, titleInput: _title.projectedValue)
Button {
_tituloFinal.wrappedValue = _title.wrappedValue
} label: {
Text("Actualizar título")
}
}
}
}
Binding manual
Generalmente se usa un Binding en conjunto con un State. Sin embargo, también es posible usar solo el Binding por medio del constructor init(get:set:) que recibe dos closures: get y set para retornar y modificar un valor.
Crear un Binding manualmente puede servir para crear una previsualización de 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("Manual binding") {
var someTitle = ""
var titleBinding = Binding<String> {
someTitle
} set: { newValue in
someTitle = newValue
}
ContentSubview(title: "Título inicial", titleInput: titleBinding)
}
A veces conviene solo pasar de referencia al Binding un valor inmutable. En este caso, se usa el método estático: .constant(_:).
#Preview("Manual binding") {
ContentSubview(title: "Título inicial", titleInput: .constant("Algún título"))
}
Creando State desde #Preview
Aunque se puede crear manualmente el Binding, es mucho más fácil usar el macro @Previewable permite usar un @State dentro de un #Preview.
#Preview("@Previewable") {
@Previewable @State var title = ""
ContentSubview(title: "Título inicial", titleInput: $title)
}

Top comments (0)