UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: How to add dynamic start and end position to animation inside UIViewRepresentable in SwiftUI

Forums > SwiftUI

I have the following UIKit animation inside a UIViewRepresentable which works fine, it animates a view in a throwing effect like animation from point A to point B. What I would like to be able to do is, set the start and the end position in ContentView when assigning the animation to a view.

Here is the code...

Static Position Animation

    import SwiftUI

        struct ContentView: 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) // how can I make this dynamic
                        })
                        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)// how can I make this dynamic
                            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)
            }
        }

How I would like to be able to call it from ContentView

    @State private var isAnimating = false
    var body: some View {
        HStack{
            Image(systemName: "circle.fill")
                .font(.system(size: 65))
                .foregroundColor(.blue)
                .throwAnimation(isAnimating: $isAnimating, startPos:CGPoint(x: 250, y: 300), endPos:CGPoint(x: 100, y: 100))
                .onTapGesture {
                    isAnimating.toggle()
                }
        }
    }
}

How can I modify this code in a way that I can enter the start and end position when assigning to a view?

Thanks!

2      

Hi, This should work:

struct ContentView: View {
    @State private var isAnimating = false
    var body: some View {
        HStack{
            Image(systemName: "circle.fill")
                .font(.system(size: 65))
                .foregroundColor(.blue)
                .throwAnimation(isAnimating: $isAnimating, startPos: CGPoint(x: 250, y: 300), endPos: CGPoint(x: 100, y: 100))
                .onTapGesture {
                    isAnimating.toggle()
                }
        }
    }
}

struct ThrowAnimationWrapper<Content: View>: UIViewRepresentable{
    @ViewBuilder let content: () -> Content
    @Binding var isAnimating: Bool
    var startPos: CGPoint
    var endPos: CGPoint

    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 =  self.startPos // how can I make this dynamic
                })
                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 =  self.endPos// how can I make this dynamic
                    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>, startPos: CGPoint, endPos: CGPoint) -> some View {
        modifier(ThrowAnimationViewModifier(isAnimating: isAnimating, startPos: startPos, endPos: endPos))
    }
}
struct ThrowAnimationViewModifier: ViewModifier {
    @Binding var isAnimating: Bool
    var startPos: CGPoint
    var endPos: CGPoint
    func body(content: Content) -> some View {
        ThrowAnimationWrapper(content: {
            content
        }, isAnimating: $isAnimating, startPos: startPos, endPos: endPos)
    }
}

3      

Thanks a LOT for your help, I appreciate it.

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.