DEV Community

GoyesDev
GoyesDev

Posted on

[SUI] Preferences

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 closure nextValue

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
  })
)
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Notar que al cambiar el título, las dimensiones del Text cambian:

Ocurre un cambio también cuando se rota el dispositivo:


Bibliografía

Top comments (0)