La aplicación debe proveer una única fuente de datos que pueda ser accedida y modificada por las vistas, a la que se conoce como "el Modelo".
@State controla el estado de una sola vista y es privado. El modelo es un objeto que se pasa a las vistas y reporta cambios al sistema.
El macro @Observable (parte del framework Observation) hace que una clase conforme el protocolo Observable y expone todos los atributos con observadores, exceptuando aquellos marcados con el macro @ObservationIgnored.
@Observable
class ApplicationData {
var title = "Default title"
var input = ""
}
struct ContentView: View {
var appData = ApplicationData()
var body: some View {
VStack(spacing: 8) {
Text(appData.title)
.padding(10)
Button {
appData.title = "New Title"
} label: {
Text("Guardar")
}
}
.padding(10)
}
}
En el ejemplo anterior, no es necesario marcar ApplicationData con @State a title y input, sino que el macro @Observable automáticamente genera observadores para las dos. Por otro lado, cuando se presiona el botón del ejemplo, la etiqueta se actualiza con el nuevo título "New Title".
Creando binding con Bindable
Si se necesita pasar un Binding hacia un Observable, se debe anotar la referencia al Observable con el property-wrapper Bindable.
struct ContentView: View {
@Bindable var appData = ApplicationData()
var body: some View {
VStack(spacing: 8) {
Text(appData.title)
.padding(10)
TextField("Nuevo título", text: $appData.input)
Button {
appData.title = appData.input
} label: {
Text("Guardar")
}
}
.padding(10)
}
}
Separar responsabilidades
ApplicationData debería contener los datos comunes para todo el sistema. De ser necesario almacenar información para una vista específica View1 se le puede crear su propio modelo de datos como ViewData1 o ViewModel1, también anotándolo con el macro @Observable.
@Observable
class ContentViewModel {
var title = "Default title"
var input = ""
}
struct ContentView: View {
private let appData = ApplicationData.shared
@Bindable private var viewModel = ContentViewModel()
var body: some View {
VStack(spacing: 8) {
Text(viewModel.title)
.padding(10)
TextField("Nuevo título", text: $viewModel.input)
Button {
viewModel.title = viewModel.input
} label: {
Text("Guardar")
}
}
.padding(10)
}
}
Ignorar cambios de alguna propiedad dentro de @Observable
Puede ser posible que no sea necesario actualizar la vista cada vez que una propiedad cambia. En ese caso, hay que marcar esa propiedad con el macro @ObservationIgnored.
En el siguiente ejemplo, a pesar de que el botón está modificando la propiedad counter de ContentViewModel, la vista no se actualiza debido a que está marcado con @ObservationIgnored.
@Observable
class ContentViewModel {
var title = "Default title"
@ObservationIgnored var counter = 0
}
struct ContentView: View {
private let appData = ApplicationData.shared
@Bindable private var viewModel = ContentViewModel()
var body: some View {
VStack(spacing: 8) {
Text("\(viewModel.title) - \(viewModel.counter)")
.padding(10)
Button {
viewModel.counter += 1
} label: {
Text("Actualizar")
}
}
.padding(10)
}
}
Actualizar una vista "manualmente"
A veces es necesario actualizar una vista cuando cambia un valor en el ViewModel que no tiene observador. En estos casos, se le puede asignar un nuevo ID con id(_:).
Cada vez que la identidad de una vista cambia, el sistema cree que es una nueva vista y por eso actualiza la interfaz para mostrar el cambio.
Teniendo en cuenta lo anterior, considerar el siguiente ejemplo, donde se cambia el id del Text cuando se presiona el botón, para que actualice los cambios provenientes del ViewModel.
@Observable
class ContentViewModel {
var title = "Default title"
@ObservationIgnored var counter = 0
}
struct ContentView: View {
private let appData = ApplicationData.shared
@State private var titleId = UUID()
@Bindable private var viewModel = ContentViewModel()
var body: some View {
VStack(spacing: 8) {
Text("\(viewModel.title) - \(viewModel.counter)")
.id(titleId)
.padding(10)
Button {
viewModel.counter += 1
titleId = UUID()
} label: {
Text("Actualizar")
}
}
.padding(10)
}
}




Top comments (0)