VKP.Riot
VKP.Riot

Reputation: 25

Animation completion block goes haywire after dismissing view

I have a detail viewcontroller that contains a repeating carousel with an animating image view like so:

func animateCarousel1(){
    UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations: 
        //image animation
    },completion: { (_) -> Void in
        print("animation 1 complete")
        self.animateCarousel2() 
                  })
    }
func animateCarousel2(){
    UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations: 
        //image animation
    },completion: { (_) -> Void in
        print("animation 2 complete")
        self.animateCarousel1() 
                  })
    }

Upon popping this view and returning to the parent view, I see that in the debug console, the functions are continuing to be called in the background, repeatedly and simultaneously, infinitely.

CPU usage also jumps to 90% in the simulator as well.

Is there some sort of deinit I need to do before popping the viewcontroller? I can't seem to wrap my head around this one.

Upvotes: 2

Views: 692

Answers (3)

Mridul Gupta
Mridul Gupta

Reputation: 609

The reason is in animation block, you are using strong reference of self which is why when you are poping this viewcontroller its reference count is still not 0, because of which ARC is not able to dealloc the reference of this view controller.

There is a concept of [weak self]. You can modify your code like below and then after poping you won't see those method calls in your debug console. Reason: weak self will not increase the reference count and ARC will be able to remove the reference of the object

func startAnimation() {
    UIView.animate(withDuration: 0.4, animations: {[weak self] in
        self?.animateView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        print("startAnimation")
    }, completion: {[weak self]
        finished in
        self?.startAnimation2()
    })
}
func startAnimation2() {
    UIView.animate(withDuration: 0.4, animations: {[weak self] in
        self?.animateView.frame = CGRect(x: 100, y: 0, width: 100, height: 100)
        print("startAnimation2")
    }, completion: {[weak self]
        finished in
        self?.startAnimation()
    })
}

Upvotes: 1

Lito
Lito

Reputation: 2328

It's possible this solve the problem you can see this post link here

Use on deinit() controller method or viewDidDissapear(), try the following code

deinit {
    print("deinit \(NSStringFromClass(self.classForCoder).components(separatedBy: ".").last ?? "")") // print viewcontroller deallocated
    self.view_for_remove_animations.layer.removeAllAnimations() // Forze delete animations
}

OR

 override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    print("viewDidDisappear \(NSStringFromClass(self.classForCoder).components(separatedBy: ".").last ?? "")") // print viewcontroller deallocated
    self.view_for_remove_animations.layer.removeAllAnimations() // Forze delete animations
}

Try it, and send feedback

Upvotes: 1

Rob
Rob

Reputation: 437392

The issue is that completion blocks are called even if the animation finishes. So there are a few possible solutions:

  1. Check the finished parameter of the completion handlers:

    func animateCarousel1() {
        UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations: {
            //image animation
        }, completion: { finished in
            print("animation 1 complete")
            if finished { self.animateCarousel2() }
        })
    }
    
    func animateCarousel2() {
        UIView.animate(withDuration: 1, delay: 3, options: .curveEaseInOut, animations: {
            //image animation
        }, completion: { finished in
            print("animation 2 complete")
            if finished { self.animateCarousel1() }
        })
    }
    
  2. Use a different animation technique that doesn't require the circular references between these animation routines, e.g.:

    func animateCarousel() {
        UIView.animateKeyframes(withDuration: 8, delay: 3, options: .repeat, animations: {
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.125) {
                // animation 1
            }
            UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.125) {
                // animation 2
            }
        }, completion: nil)
    }
    

Upvotes: 1

Related Questions