SwiftUI: componente Shape
Qué es Shape
Shape
es un protocolo de SwiftUI
que define formas geométricas 2D que pueden ser usadas en las vistas. Los componentes que implementan este protocolo ocupan todo el tamaño disponible en pantalla, por lo que es común definir su frame con el modificador frame
o de forma indirecta al estar incluido en otro componente.
Aquí podéis consultar la documentación oficial
El uso común de componentes que implementan el protocolo Shape
suele ser uno de los siguientes:
- Mostrar una forma geométrica en la vista.
- Recortar una vista ya existente (como un
Text
oImage
) para que, en vez de ser cuadrada (forma por defecto), tenga una forma geométrica determinada (como, por ejemplo, una imagen redondeada del avatar de un usuario).
SwiftUI
tiene por defecto varias implementaciones del protocolo Shape
, como pueden ser:
Capsule
Capsule() .fill(Color.blue) .frame(width: 200, height: 70)
Circle
Circle() .fill(Color.blue) .frame(width: 70, height: 70)
Ellipse
Ellipse() .fill(Color.blue) .frame(width: 200, height: 100)
Rectangle
Rectangle() .fill(Color.blue) .frame(width: 200, height: 70)
RoundedRectangle
RoundedRectangle(cornerRadius: 25) .fill(Color.blue) .frame(width: 200, height: 70)
Los ejemplos que vemos en las capturas anteriores los veremos con los modificadores que se explicarán a continuación.
Modificadores comunes para Shape
El protocolo Shape
incluye nuevos modificadores propios adicionales a los de View
. Siempre que queramos hacer uso de ellos primero deberán usarse los de Shape
y luego los de View
, ya que al usar los de View
perdemos la referencia de Shape
.
Los modificadores de View
podemos consultarlos en el siguiente enlace.
fill
Permite rellenar el contenedor con un color o gradiente.
Capsule() .fill(Color.blue) .frame(width: 200, height: 70)
stroke
Permite definir el borde del contenedor con un color o gradiente.
Capsule() .stroke(LinearGradient(gradient: Gradient(colors: [Color.orange, Color.green]), startPoint: .leading, endPoint: .trailing), lineWidth: 5) .frame(width: 200, height: 70)
También permite aplicar un estilo al borde para conseguir diferentes efectos.
Capsule() .stroke(Color.red, style: StrokeStyle(lineWidth: 5, dash: [10])) .frame(width: 200, height: 70)
trim
Permite recortar el contenedor.
Circle() .trim(from: 0, to: 0.5) .fill(Color.blue) .frame(width: 200, height: 70)
Cómo modificar la forma de un View con clipShape
Todas las vistas de SwiftUI
tienen a su disposición el modificador clipShape
que permite indicar un Shape
que indica los límites visibles de la vista, lo que permitirá conseguir un efecto por el que cualquier View
tenga la forma del Shape
indicado.
Text("Hello, World!") .font(.title) .padding() .background(Color.blue) .foregroundColor(.white) .clipShape(Capsule())
En este ejemplo se ha indicado Capsule
, pero podríamos modificarlo por otro Shape
como Ellipse
.
Cómo modificar la forma de un View con clipShape y añadir un borde con stroke
Como en el apartado anterior, para dar forma a un View
tenemos que usar el modificador clipShape
para definir los límites de la vista. En cambio, para añadir un borde hay que usar el modificador overlay
y añadir un nuevo Shape
del mismo tipo que en clipShape
, pero añadiéndole el modificador stroke
con la configuración deseada.
Text("Hello, World!") .font(.title) .padding() .background(Color.blue) .foregroundColor(.white) .clipShape(Capsule()) .overlay( Capsule() .stroke(LinearGradient(gradient: Gradient(colors: [Color.orange, Color.green]), startPoint: .leading, endPoint: .trailing), lineWidth: 5) )
Cómo animar el borde de un Shape
Modificando un poco el ejemplo anterior podemos conseguir que el borde que hemos añadido tenga una animación por la que se vaya rellenando poco a poco. Para conseguir este efecto usaremos el modificador trim
sobre el Shape
añadido en el overlay
, usando como parámetros dos variables de estado que se irán modificando a partir de un Timer
.
La vista se suscribirá a los cambios del Timer
a través del modificador onReceive
, permitiéndonos modificar los valores de las variables de estado a los que deseemos para conseguir el efecto.
struct ContentView: View { private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() @State private var trimFrom: CGFloat = 0 @State private var trimTo: CGFloat = 0 var body: some View { Text("Hello, World!") .font(.title) .padding() .background(Color.blue) .foregroundColor(.white) .clipShape(Capsule()) .overlay( Capsule() .trim(from: trimFrom, to: trimTo) .stroke(LinearGradient(gradient: Gradient(colors: [Color.yellow, Color.purple]), startPoint: .leading, endPoint: .trailing), lineWidth: 5) ) .onReceive(timer) { _ in withAnimation { let increment: CGFloat = 0.2 if trimFrom == 0 && trimTo != 1 { trimTo += increment if trimTo > 1 { trimTo = 1 } } else if trimTo == 1 && trimFrom != 1 { trimFrom += increment if trimFrom > 1 { trimFrom = 1 } } else if trimTo == 1 && trimFrom == 1 { trimFrom = 0 trimTo = 0 } } } } }
Es importante que la modificación de las variables de estado se encuentre dentro del bloque withAnimation
para conseguir que el cambio de valores sea animado.
Ejemplo
Puedes encontrar este ejemplo en github.com bajo el apartado Shape.
Rafael Fernández,
iOS Tech Lider