11
votes

I'm presenting a View Controller modally with a blur background effect. iOS 10/XCode 8 introduced a problem with my animation. This is the presentation code:

let modalVC = ModalViewController(nibName: "ModalViewController", bundle: nil)
modalVC.modalTransitionStyle = .CrossDissolve
modalVC.modalPresentationStyle = .OverFullScreen
presentViewController(modalVC, animated: true, completion: nil)

Adding the blur on viewDidLoad() function in the ModalViewController:

let blurEffect = UIBlurEffect(style: .Light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = view.bounds
blurEffectView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]

view.addSubview(blurEffectView)
view.sendSubviewToBack(blurEffectView)

The ModalViewController has a clear background and I added a BlurEffectView with a Dark Blur Effect. Tried both programmatically with the previous snippet and in Interface Builder.

On iOS 8 & 9 the .CrossDissolve transition took care of the "fade", but after testing on iOS 10 (both the device and simulator) the view appears with a dark semi-transparent background color instead of the blur.

After the .CrossDissolve animation finishes, the background color changes to the actual blur effect background. Any ideas why this is happening?

Also tried adding layoutIfNeeded() at the beginning and end of viewDidLoad() for the modal view controller without any luck. I'm using swift 2.3

3
Have also discovered this annoying change in behavior. Have you tried to turn off the transition style and handle the animation manually?Johannes
Tried it, it looks better, but there is still a slight delay on the blur. Just not as noticableJohannes
There's an accessibility feature that disables blurs, for people that forgot about that. Also +1 for OP, I have the same problem. I have a table view with custom cells behind the presented VC. The blur only applies to the stuff behind at the end of the cross dissolve animation, and then suddenly blurs out the presenting VC.cloudcal

3 Answers

11
votes

You need to create a new UIViewControllerAnimatedTransitioning.

Then in animateTransition(using transitionContext: UIViewControllerContextTransitioning) you need to code your Animation.

Now in iOS 10 you can use UIViewPropertyAnimator in order to animate the BlurRadius of an UIVisualBlurEffect.

Result : enter image description here

Here you have an example of usage : https://github.com/PierrePerrin/PPBlurModalPresentation

First

You need to create you blur transition

    class BlurModalPresentation: NSObject,UIViewControllerAnimatedTransitioning {


        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval{

            return 0.5
        }

//This is the blur view used for transition
        var blurView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.light))
        var destinationView : UIView!
        var animator: UIViewPropertyAnimator?

        // This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning){

            let containerView = transitionContext.containerView

            _ = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
            let toVc = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)

            destinationView = toVc!.view
            destinationView.alpha = 0.0

//Here we add the blur view and set it effect to nil
            blurView.effect = nil
            blurView.frame = containerView.bounds

            self.blurTransition(transitionContext) { 

                self.unBlur(transitionContext, completion: { 

                    self.blurView.removeFromSuperview()
                    transitionContext.completeTransition(true)
                })
            }

            containerView.addSubview(toVc!.view)
            containerView.addSubview(blurView)
        }

        //This methods add the blur to our view and our destinationView
        func blurTransition(_ context : UIViewControllerContextTransitioning,completion: @escaping () -> Void){

            UIViewPropertyAnimator.runningPropertyAnimator(withDuration: self.transitionDuration(using: context)/2, delay: 0, options: UIViewAnimationOptions.curveLinear, animations: {

                self.destinationView.alpha = 0.5
                  self.blurView.effect = UIBlurEffect(style: UIBlurEffectStyle.light)
            }, completion: { (position) in
                completion()
            })

        }
        //This Method remove the blur view with an animation
        func unBlur(_ context : UIViewControllerContextTransitioning,completion: @escaping () -> Void){

            UIViewPropertyAnimator.runningPropertyAnimator(withDuration: self.transitionDuration(using: context) / 2, delay:0, options: UIViewAnimationOptions.curveLinear, animations: {

                self.destinationView.alpha = 1.0
                self.blurView.effect = nil
            }, completion: { (position) in
                completion()
            })
        }

    }

Then

You need to set the transitioning delegation in your ViewController:

import UIKit

class ViewController: UIViewController,UIViewControllerTransitioningDelegate {

    let blurModalPresentation = BlurModalPresentation()

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    func showVC(){

        let str = self.storyboard!
        let vc = str.instantiateViewController(withIdentifier: "YourViewControllerIdentifier")
        vc.transitioningDelegate = self
        self.present(vc, animated: true, completion: nil)
    }


    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?{

        return blurModalPresentation
    }


    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?{

        return blurModalPresentation
    }

}
1
votes

First of all, I view this solution as a temporary workaround, because I must assume this newly introduced behavior is a bug and will be fixed in future updates. This makes it slightly less noticable as the blur effect pops in during the animation instead of afterwards. Still not as nice as in iOS 9 and backwards, but slightly better.

  1. Present view controller without animation:

    presentViewController(modalVC, animated: false, completion: nil)
    
  2. Hide your view from the beginning:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        view.alpha = 0
    }
    
  3. Apply animations manually:

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
    
        UIView.animateWithDuration(0.25) {
            self.view.alpha = 1
        }
    }
    
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
    
        UIView.animateWithDuration(0.25) {
            self.view.alpha = 0
        }
    }
    
1
votes

The only right way is to create custom modal transition and animate effect property. See https://stackoverflow.com/a/39709740