Preferences permite a una vista enviar información a sus contenedores.
Para empezar se debe definir una estructura que conforme el protocolo PreferenceKey que requiere que se defina:
-
Value: Tipo de valor producido por esta preferencia -
defaultValue: Valor por defecto de la preferencia. -
reduce(value:nextValue:): Combina una secuencia de valores que modifican el valor acumulado anterior con el resultado de invocar el closurenextValue
En la documentación de defaultValue aparece una descripción de suma importancia:
Views that have no explicit value for the key produce this default value. Combining child views may remove an implicit value produced by using the default. This means that reduce(value: &x, nextValue: {defaultValue}) shouldn’t change the meaning of x.
Lo anterior sugiere que se debe definir defaultValue, Value y reduce(value:nextValue:) de forma tal que la preferencia no se vea afectada por aquellas vistas que no asignen ningún valor, o sea: que están asignando el valor por defecto de forma implícita.
Esto quiere decir que reduce(value:nextValue:) NO SE DEBE IMPLEMENTAR como value = nextValue().
Para nuestro ejemplo, el objetivo es poder cambiar el tiempo de ejecución el valor en un Text con base en el tamaño de otro, estando los dos al mismo nivel de jerarquía.
Consideremos SizeInfoPreference:
struct SizeInfoPreference: PreferenceKey {
typealias Value = CGSize?
static var defaultValue: CGSize?
static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
if let _nextValue = nextValue() {
value = _nextValue
}
}
}
Se puede ver que defaultValue es nil y reduce(value:nextValue:) solo actualiza value si nextValue() produce un valor distinto de nil.
Considerar ahora ContentView:
struct ContentView: View {
@State private var title = "Este es un texto largo. Vamos a caracterizarlo."
@State private var input = ""
@State private var textSize: CGSize = .zero
var body: some View {
VStack(spacing: 10) {
Text(title)
.font(.title)
.multilineTextAlignment(.center)
.background(
GeometryReader(content: { proxy in
Color.clear
.preference(key: SizeInfoPreference.self, value: proxy.size)
})
)
.border(.blue.opacity(0.3))
.padding(.top, 20)
.padding(.leading, 200)
.border(.gray)
Text("Dimensiones: \(textSize.width)x\(textSize.height)")
TextField("Nuevo título", text: $input)
Button("Actualizar título") {
title = input
}
}
.border(.red.opacity(0.3))
.onPreferenceChange(SizeInfoPreference.self) { newSize in
if let newSize {
textSize = newSize
}
}
}
}
Para inyectar un valor en el PreferenceKey, se usa preference(key:value:). Esto se puede ver en ContentView cuando el Color.clear inyecta el tamaño del GeometryReaderProxy:
.background(
GeometryReader(content: { proxy in
Color.clear
.preference(key: SizeInfoPreference.self, value: proxy.size)
})
)
Para observar un cambio en un PreferenceKey, se aplica el modificador onPreferenceChange(_:perform:) sobre el contenedor. Esto se ilustra en ContentView cuando el VStack actualiza textSize cuando el valor de SizeInfoPreference es distinto de nil.
.onPreferenceChange(SizeInfoPreference.self) { newSize in
if let newSize {
textSize = newSize
}
}
Notar que al cambiar el título, las dimensiones del Text cambian:
Ocurre un cambio también cuando se rota el dispositivo:




Top comments (0)