Animate the fractionComplete of UIViewPropertyAnimator for blurring the background
Asked Answered
J

3

9

So I'm using the new UIViewPropertyAnimator and UIVisualEffectView to achieve the same thing as the Spotlight search when you scrolling down on the home screen and it blurs the background.

I'm using the fractionComplete property to set the procent of how much to blur when panning a UIView.

    animator = UIViewPropertyAnimator(duration: 1, curve: .linear) {
        self.blurEffectView.effect = nil
    }

And the amount of blurriness is changed with a value between 0.0 - 1.0.

        animator?.fractionComplete = blurValue

But when I cancel the pan gesture I want the blur to animate back from where it is to no blur (e.g ~ -> 1.0) with a duration of something like 0.4 milliseconds.

Right now I just set the fractionComplete to 1.0 when the pan gesture is cancelled. Instead I want to animate it.
I have tried the UIView.animate(withDuration.. but it doesn't affect the UIViewPropertyAnimators fractionComplete, and thats the only way to blur an UIVisualEffectView.

Any ideas?

Judicative answered 16/12, 2016 at 17:45 Comment(2)
I have had the same bug, and at this point I think it might be an Apple bug related to the effect being cleared instead of a value that it can transition between 0 and 1. Please make sure to file a radar with Apple.Demogorgon
just to confirm, when you start panning, the animation starts to blur the background. when you stop panning, you want it to go back to original state?Shing
K
15

It seems that fractionComplete has a bug (my question on Stackoverflow: UIViewPropertyAnimator does not update the view when expected), rdar://30856746. The property only sets the state from inactive to active, but does not update the view, because (I assume) there is another internal state that does not trigger.

To workaround the problem you can do this:

animator.startAnimation() // This will change the `state` from inactive to active
animator.pauseAnimation() // This will change `isRunning` back to false, but the `state` will remain as active

// Now any call of `fractionComplete` should update your view correctly!
animator.fractionComplete = /* your value here */

Here is a playground snippet to play around:

let liveView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 50))
liveView.backgroundColor = .white

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = liveView

let square = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
square.backgroundColor = .red

liveView.addSubview(square)

let animator = UIViewPropertyAnimator.init(duration: 5, curve: .linear)

animator.addAnimations {

    square.frame.origin.x = 350
}

let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blurView.frame = liveView.bounds

liveView.addSubview(blurView)

animator.addAnimations {

    blurView.effect = nil
}

// If you want to restore the blur after it was animated, you have to 
// safe a reference to the effect which is manipulated
let effect = blurView.effect

animator.addCompletion {
    // In case you want to restore the blur effect
    if $0 == .start { blurView.effect = effect }
}

animator.startAnimation()
animator.pauseAnimation()

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {

    animator.fractionComplete = 0.5
}

DispatchQueue.main.asyncAfter(deadline: .now() + 4) {

    // decide the direction you want your animation to go.
    // animator.isReversed = true
    animator.startAnimation()
}
Kitchens answered 5/3, 2017 at 11:54 Comment(7)
Works like a charm! Hopefully there will be a fix in the near future.Judicative
Thanks! Duped the bug. There seem to be even more problems, when you do not do the startAnimation()-pauseAnimation() dance, e.g. calling continueAnimation() does not work without a small delay.Haleakala
@Haleakala right now I'm not even sure if it's a real bug or intended. In WWDC17 Apple engineers always used pauseAnimation() without even calling startAnimation(), which seems to do the trick here as well. :/ It could also be that they're work around the bug as well. :DKitchens
@Kitchens I created a little sample project that illustrates the bug that I'm having trouble with: github.com/klaas/RadarSample_UIViewPropertyAnimatorBug (It's an Xcode template, only changed ViewController.swift).Haleakala
@Haleakala I made the same observation like flag_useWorkaround2 in my original post, but using fractionComplete instead: #42608333Kitchens
@Kitchens Thanks for having a look. I'll create another radar with my sample code...Haleakala
I don't think it's a bug. Here's a snippet from the fractionComplete's documentation: You can update the value of this property only while the animator is paused. Changing the value of this property on an inactive animator moves it to the active state..Mil
S
4

If you're still looking for a way to actually animate fractionComplete without the use of a slider or a gesture, I was quite happy with my results using a CADisplayLink. You can see my results here: https://gist.github.com/vegather/07993d15c83ffcd5182c8c27f1aa600b

Sarthe answered 6/12, 2017 at 2:51 Comment(0)
N
0

I've used a delayed loop to decrease "fractionComplete"

func resetAnimator(){
        
        let duration = 1.0 / 60.0
        
        if self.animator.fractionComplete > 0 {
            self.animator.fractionComplete -= 0.06
            delay(duration, closure: {
                self.resetAnimator()
            })
        }
    }

func delay(_ delay:Double, closure:@escaping ()->()) {
        DispatchQueue.main.asyncAfter(
            deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
    }
Nemesis answered 26/6, 2020 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.