Crear modificadores personalizados con ViewModifier
Misael Cuevas
15 mayo, 20245min de lectura
El protocolo ViewModifier
en SwiftUI permite la creación de modificadores personalizados para adaptar los controles según nuestras preferencias. Estos modificadores pueden aplicarse a los elementos de la interfaz de usuario de la misma manera que los modificadores predeterminados. Es común encontrarse con la necesidad de utilizar un control personalizado en varias ocasiones dentro de una vista o diferentes vistas. En lugar de duplicarlo, podemos utilizar ViewModifier
para centralizar la personalización del control en un único lugar, simplificando así la estructura de nuestra vista en SwiftUI. Cualquier modificación realizada se aplicará automáticamente a todos los controles sin necesidad de esfuerzo adicional.
Creación de un ViewModifier
Para crear tu primer ViewModifier
, considera una vista de SwiftUI llamada MyView
que muestra tres avatares:
import SwiftUIstruct MyView: View {var body: some View {VStack(spacing: 10) {Circle().fill(Color.blue).frame(width: 150, height: 150).overlay(Text("AC").font(.largeTitle).foregroundColor(.white))Circle().fill(Color.red).frame(width: 150, height: 150).overlay(Text("MC").font(.largeTitle).foregroundColor(.white))Circle().fill(Color.purple).frame(width: 150, height: 150).overlay(Text("NL").font(.largeTitle).foregroundColor(.white))}}}
Estos tres elementos están compuestos por un Circle
y un Text
superpuesto, con características idénticas. Esta es una situación ideal para utilizar ViewModifier
. Al finalizar este artículo, habrás creado un modificador que convertirá cualquier Text
en un avatar.
Para comenzar, crea una estructura llamada AvatarModifier
que adopte el protocolo ViewModifier
:
struct AvatarModifier: ViewModifier {func body(content: Content) -> some View {}}
La función body
debe devolver una vista y toma el parámetro content
, que hace referencia al control al que se aplicará el modificador. Dentro de esta función, agrega lo siguiente:
// 1Circle().fill(Color.blue).frame(width: 150, height: 150).overlay(// 2content.font(.largeTitle).foregroundColor(.white))
- Agrega un
Circle
con los mismos modificadores que en la vistaMyView
. - Reemplaza el
Text
concontent
para referenciar al control al que se aplicará el modificador.
Una vez hecho esto, puedes aplicar AvatarModifier
a cualquier control utilizando el modificador modifier
, por ejemplo:
Text("MC").modifier(AvatarModifier())
Actualiza la vista MyView
utilizando AvatarModifier
de la siguiente manera:
import SwiftUIstruct MyView: View {var body: some View {VStack(spacing: 20) {// 1Text("AC").modifier(AvatarModifier())Text("MC").modifier(AvatarModifier())Text("NL").modifier(AvatarModifier())}}}
- Cada text utiliza el modificador
AvatarModifier
.
Ahora, la vista es más sencilla, ya que la personalización del control se ha centralizado en un modificador. Además, puedes utilizar el modificador de manera similar a los modificadores predeterminados de SwiftUI extendiendo el protocolo View
:
// 1extension View {// 2func avatar() -> some View {// 3modifier(AvatarModifier())}}
- Se extiende del protocolo
View
. - Se crea una función llamada
avatar
que devuelve una vista. - Devuelve el modificador
AvatarModifier
.
Reemplaza .modifier(AvatarModifier())
por .avatar()
en MyView
.
Agregar propiedades
Una diferencia notable entre la vista actual y la inicial es que cada fondo del avatar tiene un color fijo. Para permitir diferentes colores de fondo, es necesario agregar propiedades al AvatarModifier
. En AvatarModifier
, agrega la siguiente propiedad encima de la función body
:
let backgroundColor: Color
Luego, modifica .fill(Color.blue)
por .fill(backgroundColor)
. Asimismo, actualiza la función avatar en la extensión View
para que acepte el parámetro backgroundColor
:
extension View {// 1func avatar(backgroundColor: Color) -> some View {modifier(// 2AvatarModifier(backgroundColor: backgroundColor))}}
- Se añade el parámetro
backgroundColor
. - Se utiliza
backgroundColor
en el modificadorAvatarModifier
.
Finalmente, en la vista MyView
, reemplaza el uso de .avatar()
por .avatar(backgroundColor: TU_COLOR)
, por ejemplo:
Text("AC").avatar(backgroundColor: .blue)
Gracias al uso de propiedades, puedes personalizar tu modificador con diferentes configuraciones sin esfuerzo adicional. ¿Te gustaría agregar más propiedades a AvatarModifier
? Puedes incluir propiedades para personalizar el tamaño del Circle
o el tipo y color de fuente del content
.
Agregar variables de estado
Una ventaja de los modificadores personalizados es que puedes agregar variables de estado, igual que en cualquier otra vista de SwiftUI. Para demostrarlo, vamos a agregar una variable de estado booleana a AvatarModifier
. Cada vez que se presione un avatar, cambiará su estado para producir una animación sencilla.
Encima de la propiedad backgroundColor
, agrega la variable de estado booleana isTapped
con el valor predeterminado false
:
@State private var isTapped = false
Después del modificador .overlay
del Circle
, agrega lo siguiente:
// 1.scaleEffect(isTapped ? 0.9 : 1.0)// 2.onTapGesture {withAnimation {isTapped.toggle()}}
- Agrega el modificador
.scaleEffect
para aplicar una escala diferente según el valor deisTapped
. - Agrega el modificador
.onTapGesture
para detectar cuando se presiona el avatar y cambiar el valor deisTapped
con animación.
Al ejecutar el código y presionar en los avatares, observarás cómo se reproduce la animación:
Código completo
import SwiftUIstruct MyView: View {var body: some View {VStack(spacing: 20) {Text("AC").avatar(backgroundColor: .blue)Text("MC").avatar(backgroundColor: .red)Text("NL").avatar(backgroundColor: .purple)}}}struct AvatarModifier: ViewModifier {@State private var isTapped = falselet backgroundColor: Colorfunc body(content: Content) -> some View {Circle().fill(backgroundColor).frame(width: 150, height: 150).overlay(content.font(.largeTitle).foregroundColor(.white)).scaleEffect(isTapped ? 0.9 : 1.0).onTapGesture {withAnimation {isTapped.toggle()}}}}extension View {func avatar(backgroundColor: Color) -> some View {modifier(AvatarModifier(backgroundColor: backgroundColor))}}