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
TextoImage) 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
Aeronautics