I have the following Shape in which I've attempted to draw the swift bird.
struct SwiftBird: Shape {
func path(in rect: CGRect) -> Path {
let size = min(rect.width, rect.height)
let path = Path { path in
path.move(to: CGPoint(x: 0.565 * size, y: 0.145 * size))
path.addQuadCurve(to: CGPoint(x: 0.79 * size, y: 0.62 * size), control: CGPointMake(0.85 * size, 0.34 * size))
path.move(to: CGPoint(x: 0.79 * size, y: 0.62 * size))
path.addQuadCurve(to: CGPoint(x: 0.845 * size, y: 0.825 * size), control: CGPointMake(0.88 * size, 0.75 * size))
path.move(to: CGPoint(x: 0.1 * size, y: 0.58 * size))
path.addQuadCurve(to: CGPoint(x: 0.5 * size, y: 0.82 * size), control: CGPointMake(0.25 * size, 0.8 * size))
path.addQuadCurve(to: CGPoint(x: 0.67 * size, y: 0.775 * size), control: CGPointMake(0.6 * size, 0.82 * size))
path.addQuadCurve(to: CGPoint(x: 0.845 * size, y: 0.825 * size), control: CGPointMake(0.78 * size, 0.715 * size))
path.move(to: CGPoint(x: 0.1 * size, y: 0.58 * size))
path.addQuadCurve(to: CGPoint(x: 0.525 * size, y: 0.63 * size), control: CGPointMake(0.325 * size, 0.735 * size))
path.move(to: CGPoint(x: 0.175 * size, y: 0.25 * size))
path.addQuadCurve(to: CGPoint(x: 0.525 * size, y: 0.63 * size), control: CGPointMake(0.305 * size, 0.445 * size))
path.move(to: CGPoint(x: 0.175 * size, y: 0.25 * size))
path.addQuadCurve(to: CGPoint(x: 0.475 * size, y: 0.475 * size), control: CGPointMake(0.36 * size, 0.405 * size))
path.move(to: CGPoint(x: 0.26 * size, y: 0.205 * size))
path.addQuadCurve(to: CGPoint(x: 0.475 * size, y: 0.475 * size), control: CGPointMake(0.4 * size, 0.405 * size))
path.move(to: CGPoint(x: 0.26 * size, y: 0.205 * size))
path.addQuadCurve(to: CGPoint(x: 0.63 * size, y: 0.505 * size), control: CGPointMake(0.465 * size, 0.405 * size))
path.move(to: CGPoint(x: 0.565 * size, y: 0.145 * size))
path.addQuadCurve(to: CGPoint(x: 0.63 * size, y: 0.505 * size), control: CGPointMake(0.7 * size, 0.355 * size))
}
return path
}
}
I add and animate the drawing of the bird in another view like so:
struct TestView: View {
@State private var percentage: CGFloat = 0
var body: some View {
ZStack {
Color(.black)
SwiftBird()
.trim(from: 0, to: percentage)
.stroke(Color.white, style: StrokeStyle(lineWidth: 1, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)
.animation(.easeInOut(duration: 5.0), value: percentage)
.onAppear {
self.percentage = 1.0
}
}
}
}
I'd like to fill the shape with color once the drawing of the paths is complete. However if I apply .fill(Color.orange)
modifier to the shape it doesn't fill the shape inside correctly. I am able to see black areas inside the shape and also the color overruns the paths. How can I achieve the color fill with animation? Any help is appreciated.
Your shape looks like this when filled:
The problem is that your shape isn't a single closed subpath. It's a bunch of disconnected subpaths. Every move(to:)
starts a new subpath. When you fill the shape, each subpath gets closed and filled individually. Most of the logo's interior isn't enclosed by any of the subpaths, and some of the exterior is enclosed by the individually-closed subpaths.
Here's a version that creates a single closed subpath:
struct SwiftBird: Shape {
func path(in rect: CGRect) -> Path {
let size = min(rect.width, rect.height)
var path = Path()
path.move(to: CGPoint(x: 0.565, y: 0.145))
for (ex, ey, cx, cy): (CGFloat, CGFloat, CGFloat, CGFloat) in [
(0.79, 0.62, 0.85, 0.34), // Upper wing outside
(0.845, 0.825, 0.88, 0.75), // Head top
(0.67, 0.775, 0.78, 0.715), // Head bottom
(0.5, 0.82, 0.6, 0.82), // Lower shoulder
(0.1, 0.58, 0.25, 0.8), // Lower wing outside
(0.525, 0.63, 0.325, 0.735), // Lower wing inside
(0.175, 0.25, 0.305, 0.445), // Lower tail outside
(0.475, 0.475, 0.36, 0.405), // Lower tail inside
(0.26, 0.205, 0.4, 0.405), // Upper tail inside
(0.63, 0.505, 0.465, 0.405), // Upper tail outside
(0.565, 0.145, 0.7, 0.355), // Upper wing inside
] {
path.addQuadCurve(
to: CGPoint(x: ex, y: ey),
control: CGPoint(x: cx, y: cy)
)
}
path.closeSubpath()
path = path.applying(.init(scaleX: size, y: size))
return path
}
}
It looks the same as yours when stroked:
But fills correctly:
One way to make SwiftUI fill the logo precisely at the end of the animation is to wrap it in a custom Animatable
View
:
struct AnimatedSwiftLogo: View, Animatable {
var animatableData: Double
var body: some View {
ZStack {
SwiftBird()
.fill(animatableData == 1 ? .orange : .clear)
SwiftBird()
.trim(from: 0, to: animatableData)
.stroke(Color.white, style: StrokeStyle(lineWidth: 1, lineCap: .round, lineJoin: .round))
}
}
}
struct TestView: View {
@State private var percentage: CGFloat = 0
var body: some View {
ZStack {
Color(.black)
AnimatedSwiftLogo(animatableData: percentage)
.frame(width: 100, height: 100)
.animation(.easeInOut(duration: 5.0), value: percentage)
.onAppear {
self.percentage = 1.0
}
}
}
}
Result: