I am using following code to display success with circle and checkmark
struct CheckMarkView: View {
@State var borderInit: Bool = false
@State var spinArrow: Bool = false
@State var dismissArrow: Bool = false
@State var displayCheckmark: Bool = false
var body: some View {
ZStack {
Circle()
.strokeBorder(style: StrokeStyle(lineWidth: borderInit ? 10 : 64))
.frame(width: 128, height: 128)
.foregroundColor(borderInit ? .white : .black)
.animation(.easeOut(duration: 3).speed(1.5))
.onAppear() {
borderInit.toggle()
}
// Arrow Icon
Image(systemName: "arrow.2.circlepath")
.frame(width: 80, height: 80)
.font(.largeTitle)
.foregroundColor(.white)
.rotationEffect(.degrees(spinArrow ? 360 : -360))
.opacity(dismissArrow ? 0 : 1)
.animation(.easeOut(duration: 2))
.onAppear() {
spinArrow.toggle()
withAnimation(Animation.easeInOut(duration: 1).delay(1)) {
self.dismissArrow.toggle()
}
}
// Checkmark
Path { path in
path.move(to: CGPoint(x: 20, y: -40))
path.addLine(to: CGPoint(x: 40, y: -20))
path.addLine(to: CGPoint(x: 80, y: -60))
}
.trim(from: 0, to: displayCheckmark ? 1 : 0)
.stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.foregroundColor(displayCheckmark ? .white : .black)
.offset(x: 150, y: 420)
//.animation(.spring(.bouncy, blendDuration: 2).delay(2))
.animation(Animation.interpolatingSpring(stiffness: 150, damping: 10, initialVelocity: 0).delay(2))
//.animation(Animation.interpolatingSpring(mass: 1, stiffness: 100, damping: 10, initialVelocity: 0).delay(2))
.onAppear() {
displayCheckmark.toggle()
}
}
.background(.black)
}
}
When I try to add frame to CheckMarkView, image and circle displayed at different place. Checkmark should be displayed within the circle. Also checkmark animation is not smooth at end. How to resolve this issue? Any help would be appreciated.
You should make the checkmark into a Shape
. This way, SwiftUI handles the layout for you, and you don't need to get it in the right place using offset
.
Here is an example of a Shape
implementation, based on the points in your Path
struct CheckmarkShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let fifthWidth = rect.width / 5
let quarterHeight = rect.height / 4
path.move(to: .init(x: rect.minX + fifthWidth, y: rect.minY + 2 * quarterHeight))
path.addLine(to: .init(x: rect.minX + 2 * fifthWidth, y: rect.minY + 3 * quarterHeight))
path.addLine(to: .init(x: rect.minX + 4 * fifthWidth, y: rect.minY + quarterHeight))
return path
}
}
The CheckmarkView
can look something like this:
@State var borderInit: Bool = false
@State var spinArrow: Bool = false
@State var dismissArrow: Bool = false
@State var displayCheckmark: Bool = false
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
Circle()
.strokeBorder(style: StrokeStyle(lineWidth: borderInit ? 10 : 64))
.frame(width: 128, height: 128)
.foregroundColor(borderInit ? .white : .black)
.animation(.easeOut(duration: 3).speed(1.5), value: borderInit)
.onAppear() {
borderInit.toggle()
}
// Arrow Icon
Image(systemName: "arrow.2.circlepath")
.frame(width: 80, height: 80)
.font(.largeTitle)
.foregroundColor(.white)
.rotationEffect(.degrees(spinArrow ? 360 : -360))
.opacity(dismissArrow ? 0 : 1)
.animation(.easeOut(duration: 2), value: dismissArrow)
.animation(.easeOut(duration: 2), value: spinArrow)
.onAppear() {
spinArrow.toggle()
withAnimation(Animation.easeInOut(duration: 1).delay(1)) {
self.dismissArrow.toggle()
}
}
CheckmarkShape()
.trim(from: 0, to: displayCheckmark ? 1 : 0)
.stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 100, height: 100)
.foregroundColor(displayCheckmark ? .white : .black)
.animation(.bouncy.delay(2), value: displayCheckmark)
.onAppear() {
displayCheckmark.toggle()
}
}
}
You should pass a value
parameter to all the animation
modifiers. The version without value
is deprecated.
Since you used a spring animation, I assume you want the checkmark's stroke to go a bit beyond where it's supposed to end, and then bounce back. You can achieve this by extending the arrow's path in the Shape
implementation, and animating the trim
to something less than 1.
struct CheckmarkShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let fifthWidth = rect.width / 5
let quarterHeight = rect.height / 4
path.move(to: .init(x: rect.minX + fifthWidth, y: rect.minY + 2 * quarterHeight))
path.addLine(to: .init(x: rect.minX + 2 * fifthWidth, y: rect.minY + 3 * quarterHeight))
path.addLine(to: .init(x: rect.minX + 5 * fifthWidth, y: rect.minY))
return path
}
}
.trim(from: 0, to: displayCheckmark ? 0.75 : 0)