Issue
This Content is from Stack Overflow. Question asked by fs_tigre
What would be the best animation method in SwiftUI
to reproduce the UIKit
animation shown below?
What I need is basically a throwing effect starting from point A and ending at Point B. I don’t need any bouncing effects, this is more of an effect to let the user know that something was added from point A to Point B; similar to the effect we get when we add a photo to an album in Photos.
I tried using .rotationEffect()
but the animation I get is too circular and with no option to add the point locations. I need a more natural throwing-paper-like
animation where you define three points, start, apex and end, see the image below.
Any suggestions?
What I have tried in SwiftUI that cannot make it look like a throwing paper animation.
struct KeyframeAnimation: View {
@State private var ascend = false
@State private var ascendDescend = false
@State private var descend = false
var body: some View {
ZStack{
VStack{
ZStack{
Image(systemName: "circle.fill")
.font(.title)
.offset(x: -157)
.foregroundColor(Color.red)
.rotationEffect(.degrees(ascend ? 17: 0))
.rotationEffect(.degrees(ascendDescend ? 145: 0))
.rotationEffect(.degrees(descend ? 18: 0))
.onAppear{
withAnimation(Animation.easeInOut(duration: 0.5)){
self.ascend.toggle()
}
withAnimation(Animation.easeInOut(duration: 1)){
self.ascendDescend.toggle()
}
withAnimation(Animation.easeInOut(duration: 2).delay(8)){
self.descend.toggle()
}
}
}
}
}
}
}
UIKit Animation
This is what I need.
@IBAction func startAnimation(_ sender: Any) {
UIView.animateKeyframes(withDuration: 3.0, delay: 0.0, options: [.calculationModeCubic], animations: {
// start
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0/5.0, animations: {
self.myView.center = CGPoint(x: self.pointA.center.x, y: self.pointA.center.y)
})
// apex
UIView.addKeyframe(withRelativeStartTime: 1.0/5.0, relativeDuration: 1.0/5.0, animations: {
self.myView.center = CGPoint(x: self.pointB.center.x - 100, y: self.pointB.center.y - 100)
})
// end
UIView.addKeyframe(withRelativeStartTime: 2.0/5.0, relativeDuration: 1.0/5.0, animations: {
self.myView.center = CGPoint(x: self.pointB.center.x, y: self.pointB.center.y)
})
}
)
}
Solution
I gave up after spending a lot of time trying to do the animation in SwfitUI and ended up reusing the existing UIKit animation using a UIViewRepresentable
as suggested in the following thread. Thank you, @ loremipsum.
Animating a SwiftUI view with UIView.animate
Working Code by Reusing the Existing UIKit Animation
import SwiftUI
struct WrapUIKitAnimationInSwiftUI: View {
@State private var isAnimating = false
var body: some View {
HStack{
Image(systemName: "circle.fill")
.font(.system(size: 65))
.foregroundColor(.blue)
.throwAnimation(isAnimating: $isAnimating)
.onTapGesture {
isAnimating.toggle()
}
}
}
}
struct ThrowAnimationWrapper<Content: View>: UIViewRepresentable{
@ViewBuilder let content: () -> Content
@Binding var isAnimating: Bool
func makeUIView(context: Context) -> UIView {
UIHostingController(rootView: content()).view
}
func updateUIView(_ uiView: UIView, context: Context) {
if isAnimating{
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.2, animations: {
uiView.center = CGPoint(x: 250, y: 300)
})
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.9, animations: {
uiView.center = CGPoint(x: 100 + 75, y: 100 - 50 )
uiView.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
})
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.7, animations: {
uiView.center = CGPoint(x: 100, y: 100)
uiView.transform = CGAffineTransform(scaleX: 0.2, y: 0.2)
})
}, completion: { _ in
uiView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
}
}
}
extension View {
func throwAnimation( isAnimating: Binding<Bool>) -> some View {
modifier(ThrowAnimationViewModifier(isAnimating: isAnimating))
}
}
struct ThrowAnimationViewModifier: ViewModifier {
@Binding var isAnimating: Bool
func body(content: Content) -> some View {
ThrowAnimationWrapper(content: {
content
}, isAnimating: $isAnimating)
}
}
This Question was asked in StackOverflow by fs_tigre and Answered by fs_tigre It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.