Search code examples
iosswiftanimationuiviewuibezierpath

Animation with UIBezierPath - Object gets fixed on the top left corner


I am trying to create an animation in swift to make a few ballons float from the bottom of the screen to the top. However in the middle of the animation one of the balloons gets fixed on the top left corner of the screen and does not disappear even when the animation finishes.

Please watch this video to see what I am talking about:

https://i.sstatic.net/VPHBn.jpg

Here is my code. I don't really know what I am doing wrong.

func presentVictoryView() {

        blackView.isHidden = false

        for _ in 0 ... 20 {

            let objectView = UIView()
            objectView.translatesAutoresizingMaskIntoConstraints = false
            objectView.frame = CGRect(x: 50, y: 50, width: 20, height: 100)
            //objectView.backgroundColor = .clear
            //objectView.alpha = CGFloat(0.9)
            objectView.isHidden = false


            let ballon = UILabel()
            ballon.translatesAutoresizingMaskIntoConstraints = false
            ballon.frame = CGRect(x: 50, y: 50, width: 20, height: 100)
            //ballon.backgroundColor = .clear
            //ballon.alpha = CGFloat(0.9)
            ballon.text = "🎈"
            ballon.font = UIFont.systemFont(ofSize: 60)
            objectView.addSubview(ballon)


            NSLayoutConstraint.activate([
                ballon.centerXAnchor.constraint(equalTo: objectView.centerXAnchor),
                ballon.centerYAnchor.constraint(equalTo: objectView.centerYAnchor)
            ])

            blackView.addSubview(objectView)

            let randomXOffset = Int.random(in: -120 ..< 200)

            let path = UIBezierPath()
            path.move(to: CGPoint(x: 270 + randomXOffset, y: 1000))
            path.addCurve(to: CGPoint(x: 100 + randomXOffset, y: -300), controlPoint1: CGPoint(x: 300 - randomXOffset, y: 600), controlPoint2: CGPoint(x: 70 + randomXOffset, y: 300))

            let animation = CAKeyframeAnimation(keyPath: "position")
            animation.path = path.cgPath
            animation.repeatCount = 1

            animation.duration = Double.random(in: 4.0 ..< 7.0)
            //animation.timeOffset = Double(arc4random_uniform(50))

            objectView.layer.add(animation, forKey: "animate position along path")

        }

        //objectView.isHidden = true


        //self?.newGame()
    }

Thank you for your help! :)


Solution

  • You should use delegate to detect end of animation and remove bubbleView then.

    func presentVictoryView() {
    
        blackView.isHidden = false
    
        for _ in 0 ... 20 {
    
            let objectView = UIView()
            objectView.translatesAutoresizingMaskIntoConstraints = false
            objectView.frame = CGRect(x: 50, y: 50, width: 20, height: 100)
            //objectView.backgroundColor = .clear
            //objectView.alpha = CGFloat(0.9)
            objectView.isHidden = false
    
            let ballon = UILabel()
            ballon.translatesAutoresizingMaskIntoConstraints = false
            ballon.frame = CGRect(x: 50, y: 50, width: 20, height: 100)
            //ballon.backgroundColor = .clear
            //ballon.alpha = CGFloat(0.9)
            ballon.text = "🎈"
            ballon.font = UIFont.systemFont(ofSize: 60)
            objectView.addSubview(ballon)
    
            NSLayoutConstraint.activate([
                ballon.centerXAnchor.constraint(equalTo: objectView.centerXAnchor),
                ballon.centerYAnchor.constraint(equalTo: objectView.centerYAnchor)
            ])
    
            blackView.addSubview(objectView)
    
            let randomXOffset = Int.random(in: -120 ..< 200)
    
            let path = UIBezierPath()
            path.move(to: CGPoint(x: 270 + randomXOffset, y: 1000))
            path.addCurve(to: CGPoint(x: 100 + randomXOffset, y: -300), controlPoint1: CGPoint(x: 300 - randomXOffset, y: 600), controlPoint2: CGPoint(x: 70 + randomXOffset, y: 300))
    
            let animation = CAKeyframeAnimation(keyPath: "position")
            animation.path = path.cgPath
            animation.repeatCount = 1
    
            // add these
            animation.fillMode = .forwards
            animation.isRemovedOnCompletion = false
            let delegate = BubbleAnimDelegate()
            delegate.didFinishAnimation = {
                objectView.removeFromSuperview()
            }
            animation.delegate = delegate
            // upto here
    
            animation.duration = Double.random(in: 4.0 ..< 7.0)
            //animation.timeOffset = Double(arc4random_uniform(50))
    
            objectView.layer.add(animation, forKey: "animate position along path")
    
        }
        //objectView.isHidden = true
        //self?.newGame()
    }
    
    class BubbleAnimDelegate: NSObject, CAAnimationDelegate {
    
        var didFinishAnimation: (()->Void)?
    
        func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
            didFinishAnimation?()
        }
    }