swiftanimationuivisualeffectviewuiviewpropertyanimator

Adding a completion handler to UIViewPropertyAnimator in Swift


I'm trying to get a property animator to start animation when a View Controller is presented. Right now the animation is playing however the UIViewPropertyAnimator doesn't respond to the completion handler added to it.

import UIKit

final class BlurEffectView: UIVisualEffectView {
    
    deinit {
    animator?.stopAnimation(true)
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        effect = nil
        animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in
          self.effect = theEffect
        }
        animator?.pausesOnCompletion = true
   }

    private let theEffect: UIVisualEffect = UIBlurEffect(style: .regular)
    var animator: UIViewPropertyAnimator?

}

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func doSomething(_ sender: UIButton) {
        let vc = storyboard?.instantiateViewController(identifier: "second") as! SecondVC
        vc.modalPresentationStyle = .overFullScreen

        present(vc, animated: false) { //vc presentation completion handler
            
            //adding a completion handler to the UIViewPropertyAnimator
            vc.blurView.animator?.addCompletion({ (pos) in
                print("animation complete") //the problem is that this line isn't executed 
            })
            vc.blurView.animator?.startAnimation()
        }
    }
    
}
import UIKit

class SecondVC: UIViewController {

    @IBOutlet weak var blurView: BlurEffectView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

Here the UIViewPropertyAnimator completion handler is added after the Second View Controller(controller with visual effect view) is presented. I have tried moving the completion handler to different places like viewDidLoad and viewDidAppear but nothing seems to work.


Solution

  • This whole thing seems incorrectly designed.

    draw(_ rect:) is not the place to initialize your animator*, my best guess at what's happening is that vc.blurView.animator? is nil when you try to start it (have you verified that it isn't?).

    Instead, your view class could look like this**:

    final class BlurEffectView: UIVisualEffectView {
        func fadeInEffect(_ completion: @escaping () -> Void) {
            UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.5, delay: 0, options: []) {
                self.effect = UIBlurEffect(style: .regular)
            } completion: { _ in
                completion()
            }
        }
    }
    

    And you would execute your animation like this:

    present(vc, animated: false) { //vc presentation completion handler
        vc.blurView.fadeInEffect {
            // Completion here
        }
    }
    

    *draw(_ rect:) gets called every time you let the system know that you need to redraw your view, and inside you're supposed to use CoreGraphics to draw the content of your view, which is not something you're doing here.

    **Since you're not using any of the more advanced features of the property animator, it doesn't seem necessary to store it in an ivar.