Shapes, Curves, AnimateableData in SwiftUI
Updated:
Shapes, Curves, AnimateableData in SwiftUI
Custom Straight lines
By default SwiftUI actually comes with a bunch of shapes out of the box like rectangles rounded rectangles circles. By building custom and unique UI designs eventually you’ll run into a point where actually need a custom shape.
SwiftUI by actually drawing the shape from point to point on a path
// MARK: - VIEW
struct CustomShapesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
ZStack {
Triangle()
// .fill(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
// .trim(from: 0, to: 0.5)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, dash: [10]))
.foregroundColor(.blue)
.frame(width: 300, height: 300)
} //: ZSTACK
}
}
// MARK: - CUSTOM SHAPE
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.midX, y: rect.minY)) // Set Starting point
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
}
}
}
// MARK: - VIEW
struct CustomShapesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
ZStack {
Image("pic")
.resizable()
.scaledToFill()
.frame(width: 300, height: 300)
.clipShape(
Triangle()
.rotation(Angle(degrees: 180))
)
} //: ZSTACK
}
}
// MARK: - CUSTOM SHAPE
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.midX, y: rect.minY)) // Set Starting point
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
}
}
}
// MARK: - VIEW
struct CustomShapesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
ZStack {
Diamond()
.frame(width: 300, height: 300)
} //: ZSTACK
}
}
// MARK: - CUSTOM SHAPE
struct Diamond: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
let horizontalOffset: CGFloat = rect.width * 0.2
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX - horizontalOffset, y: rect.midY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX + horizontalOffset, y: rect.midY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
}
}
}
// MARK: - VIEW
struct CustomShapesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
ZStack {
Trapezoid()
.frame(width: 300, height: 150)
} //: ZSTACK
}
}
// MARK: - CUSTOM SHAPE
struct Trapezoid: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
let horizontalOffset: CGFloat = rect.width * 0.2
path.move(to: CGPoint(x: rect.minX + horizontalOffset, y: rect.minY ))
path.addLine(to: CGPoint(x: rect.maxX - horizontalOffset, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX + horizontalOffset, y: rect.minY))
}
}
}
Custom Curve Lines
Curves and arcs could be a little tricky to implement. To do that, arcs which is basically just a regular symmetrical curve and then quad curves which are a little more advanced and possibly more useful because they can connect two points and create an automatic curve between those two points
/ MARK: - VIEW
struct CustomCurvesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
ArcSample()
.stroke(lineWidth: 5)
.frame(width: 200, height: 200)
}
}
// MARK: - PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
CustomCurvesBootCamp()
}
}
// MARK: - CUSTOM SHAPE
struct ArcSample: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addArc(
center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.height / 2,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: 40),
clockwise: true)
}
}
}
// MARK: - VIEW
struct CustomCurvesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
ShapeWithArc()
.frame(width: 200, height: 200)
// .rotationEffect(Angle(degrees: 90))
}
}
// MARK: - PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
CustomCurvesBootCamp()
}
}
// MARK: - CUSTOM SHAPE
struct ShapeWithArc: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
// top left
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
// top right
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
// mid right
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
// bottom
// path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
path.addArc(
center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.height / 2,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: 180),
clockwise: false)
// mid left
path.addLine(to: CGPoint(x: rect.minX, y: rect.midY))
}
}
}
- Quad curve
// MARK: - VIEW
struct CustomCurvesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
QuadSample()
.frame(width: 200, height: 200)
}
}
// MARK: - PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
CustomCurvesBootCamp()
}
}
// MARK: - CUSTOM SHAPE
struct QuadSample: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: .zero)
path.addQuadCurve(
to: CGPoint(x: rect.maxX, y: rect.maxY),
control: CGPoint(x: rect.minX, y: rect.maxY))
}
}
}
// MARK: - VIEW
struct CustomCurvesBootCamp: View {
// MARK: - PROPERTY
// MARK: - BODY
var body: some View {
WaterShape()
.fill(LinearGradient(
gradient: Gradient(colors: [Color.blue, Color.cyan]),
startPoint: .topTrailing,
endPoint: .bottomTrailing))
.ignoresSafeArea()
}
}
// MARK: - PREVIEW
struct CustomCurvesBootCamp_Previews: PreviewProvider {
static var previews: some View {
CustomCurvesBootCamp()
}
}
// MARK: - CUSTOM SHAPE
struct WaterShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.midY))
path.addQuadCurve(
to: CGPoint(x: rect.midX, y: rect.midY),
control: CGPoint(x: rect.width * 0.25, y: rect.height * 0.40))
path.addQuadCurve(
to: CGPoint(x: rect.maxX, y: rect.midY),
control: CGPoint(x: rect.width * 0.75, y: rect.height * 0.60))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
}
}
}
Custom animated Lines
// MARK: - VIEW
struct AnimatableDataBootCamp: View {
// MARK: - PROPERTY
@State private var animate: Bool = false
// MARK: - BODY
var body: some View {
ZStack {
// RoundedRectangle(cornerRadius: animate ? 60 : 0)
RectangleWithSingleCornerAnimation(cornerRadius: animate ? 60 : 0)
.frame(width: 250, height: 250)
} //: ZSTACK
.onAppear {
withAnimation(Animation.linear(duration: 2.0).repeatForever()) {
animate.toggle()
}
}
}
}
// MARK: - PREVIEW
struct AnimatableDataBootCamp_Previews: PreviewProvider {
static var previews: some View {
AnimatableDataBootCamp()
}
}
// MARK: - CUSTOM SHAPE
struct RectangleWithSingleCornerAnimation: Shape {
var cornerRadius: CGFloat
var animatableData: CGFloat {
get { cornerRadius }
set { cornerRadius = newValue }
}
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: .zero)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
path.addArc(
center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius),
radius: cornerRadius,
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: 360),
clockwise: false)
path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY ))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
}
}
}
// MARK: - VIEW
struct AnimatableDataBootCamp: View {
// MARK: - PROPERTY
@State private var animate: Bool = false
// MARK: - BODY
var body: some View {
ZStack {
Pacman(offsetAmount: animate ? 20 : 0)
.frame(width: 250, height: 250)
} //: ZSTACK
.onAppear {
withAnimation(Animation.easeInOut.repeatForever()) {
animate.toggle()
}
}
}
}
// MARK: - PREVIEW
struct AnimatableDataBootCamp_Previews: PreviewProvider {
static var previews: some View {
AnimatableDataBootCamp()
}
}
// MARK: - CUSTOM SHAPE
struct Pacman: Shape {
var offsetAmount: Double
var animatableData: Double {
get { offsetAmount }
set { offsetAmount = newValue }
}
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.midX, y: rect.midY))
path.addArc(
center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.height / 2,
startAngle: Angle(degrees: offsetAmount),
endAngle: Angle(degrees: 360 - offsetAmount),
clockwise: false)
}
}
}
🗃 Reference
SwiftUI Thinking - https://www.youtube.com/watch?v=EHhgjOt_KFA&list=PLwvDm4Vfkdphc1LLLjCaEd87BEg07M97y&index=6
Leave a comment