Search code examples
iosswiftanimationuibezierpathcgpoint

Animate a view alongside a path without CAKeyframeAnimation


I want to animate a view alongside a path (without CAKeyframeAnimation) instead with UIViewPropertyAnimator.

For that I have a path with start- and endpoint and a controlpoint to make a quadcurve.

let path = UIBezierPath()
path.move(to: view.center)
path.addQuadCurve(to: target.center, controlPoint: CGPoint(x: view.center.x-10, y: target.center.y+160))

My animator uses a UIView.animateKeyframes animation to animate the position alongside the path.

let animator = UIViewPropertyAnimator(duration: duration, curve: .easeIn)

    animator.addAnimations {
        UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.calculationModeLinear], animations: {
            let points = 100
            for i in 1...points {
                let pos = Double(i)/Double(points)
                let x = self.quadBezier(pos: pos,
                                        start: Double(view.center.x),
                                        con: Double(view.center.x-10),
                                        end: Double(target.center.x))
                let y = self.quadBezier(pos: pos,
                                        start: Double(view.center.y),
                                        con: Double(target.center.y+160),
                                        end: Double(target.center.y))

                let duration = 1.0/Double(points)
                let startTime = duration * Double(i)
                UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: duration) {
                    view.center = CGPoint(x: x, y: y)
                }
            }
        })
    }

To calculate the points I use the same function as @rmaddy, think we have the same source here.

func quadBezier(pos: Double, start: Double, con: Double, end: Double) -> Double {
    let t_ = (1.0 - pos)
    let tt_ = t_ * t_
    let tt = pos * pos

    return Double(start * tt_) + Double(2.0 * con * t_ * pos) + Double(end * tt)
}

First I thought the calulation is wrong because it felt like the calculated points going through the controlpoint or something:

enter image description here

But after iterating through the same forloop and adding blue points to the calculated x/y positions shows that the positions are correct.

enter image description here

So I wonder why my animation don't follow that path.

I found this question and posted my question as an answer but I guess nobody recognized that.


Solution

  • View.center is changing during animation, make it fixed then there is no problem:

    try the following:
    
            let cent = view.center
            UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.beginFromCurrentState], animations: {
                let points = 100
    
                for i in 1...points {
    
                    let pos = Double(i)/Double(points)
                    let x = self.quadBezier(pos: pos,
                                            start: Double(cent.x), //here
                                            con: Double(cent.x-10), //here
                                            end: Double(square.center.x))
                    let y = self.quadBezier(pos: pos,
                                            start: Double(cent.y), //here
                                            con: Double(square.center.y+160),
                                            end: Double(square.center.y))
    
                    let duration = 1.0 / Double(points)
                    let startTime = duration * Double(i)
                    print("Animate: \(Int(x)) : \(Int(y))")
                    UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: duration) {
                       view.center = CGPoint(x: x, y: y)
    
                    }
                }
            })
        }