import SwiftUI
// Animation effect that changes size of the heart icon from 1 to 0 and then back to 1
// with a cosine function
struct Scale: GeometryEffect {
var offsetValue: Double
var animatableData: Double {
get { offsetValue }
set { offsetValue = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
let reducedValue: Double = offsetValue - floor(offsetValue)
let scale = abs(cos(CGFloat(reducedValue) * .pi))
let affineTransform = CGAffineTransform(translationX: size.width * 0.5, y: size.height * 0.5)
.scaledBy(x: scale, y: scale)
.translatedBy(
x: -size.width * 0.5,
y: -size.height * 0.5
)
return ProjectionTransform(affineTransform)
}
}
struct ImgurLike: View {
@State private var touch: Bool = false
@State private var start: Bool = true
@State private var finish: Bool = false
@State private var animate: Double = 0
var body: some View {
ZStack {
// Primary Icon button with active an inactive state
ZStack {
Image(systemName: "heart")
.resizable()
.aspectRatio(contentMode: .fit)
.opacity(touch ? 0 : 1)
.foregroundColor(Color.white)
.animation(Animation.easeInOut(duration: 0.2))
Image(systemName: "heart.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.opacity(touch ? 1 : 0)
.foregroundColor(Color(UIColor.systemGreen))
.animation(Animation.easeInOut(duration: 0.3))
.modifier(Scale(offsetValue: self.animate))
}.frame(width: 60)
// Two circles with different border stroke patterns that animate with a slight
// delay one after another.
if self.start {
Circle()
.strokeBorder(
RadialGradient(
gradient: Gradient(colors: [Color.green.opacity(0), Color.green, Color.green]),
center: .center, startRadius: 30, endRadius: 60
),
style: StrokeStyle(
lineWidth: 50,
dash: [2, 5]
)
)
.opacity(touch ? 0.8 : 0)
.scaleEffect(x: touch ? 1 : 0, y: touch ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.2))
.mask(
Circle()
.stroke(lineWidth: touch ? 0 : 60)
)
.animation(Animation.easeInOut(duration: 0.3).delay(0.1))
.frame(width: 110)
Circle()
.strokeBorder(
RadialGradient(
gradient: Gradient(colors: [Color.green.opacity(0), Color.green, Color.green]),
center: .center, startRadius: 30, endRadius: 60
),
style: StrokeStyle(
lineWidth: 50,
dash: [4, 5],
dashPhase: 1
)
)
.opacity(touch ? 0.8 : 1)
.scaleEffect(x: touch ? 1 : 0, y: touch ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.2).delay(0.4))
.mask(
Circle()
.stroke(lineWidth: touch ? 0 : 60)
)
.animation(Animation.easeInOut(duration: 0.3).delay(0.5))
.frame(width: 110)
}
}
.frame(width: 120)
.onTapGesture{
self.touch.toggle()
// We have to keep track of start and finish state to make sure that some elements of the animation
// are shown during inactive -> active transition and hidden during active -> inactive transition
if self.touch {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
if self.touch {
self.finish = true
}
}
} else {
self.start = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if !self.touch {
self.start = true
self.finish = false
}
}
}
withAnimation(Animation.timingCurve(0.57,2.12,0.69,0.74, duration: 0.7).delay(0.1)) {
self.animate += 1
}
}
}
}
Imgur Like button animation in SwiftUI
by Kane
Continuing the "Like" button theme - I've made a SwiftUI version of Imgur Like button animation.
Compared to Spotify version this one is much more straightforward - uses just one GeometryEffect and the rest is done using .animation() modifiers.
Compared to Spotify version this one is much more straightforward - uses just one GeometryEffect and the rest is done using .animation() modifiers.