34

Title pretty much asks it all...

I'm playing with the iOS8 Visual Effect View with Blur. It's over a UIImageView that shows a user choosable background photo. The view placed in the contentView itself is my own custom view, shows a sort of graph/calendar. Most of said calendar graph is transparent. The blur it applies to the photo behind it is really heavy. I'd like to let more of the detail leak through. But Apple only seems to give three canned values:

typedef enum {
    UIBlurEffectStyleExtraLight,
    UIBlurEffectStyleLight,
    UIBlurEffectStyleDark 
} UIBlurEffectStyle;

I've monkied around with different alphas and background colors of the UIVisualEffectView (even though the documentation warns against that), but that doesn't do anything but make it worse.

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167

11 Answers11

47

It's a pity that Apple did not provide any options for blur effect. But this workaround worked for me - animating the blur effect and pausing it before completion.

func blurEffectView(enable enable: Bool) {
    let enabled = self.blurView.effect != nil
    guard enable != enabled else { return }

    switch enable {
    case true:
        let blurEffect = UIBlurEffect(style: .ExtraLight)
        UIView.animateWithDuration(1.5) {
            self.blurView.effect = blurEffect
        }

        self.blurView.pauseAnimation(delay: 0.3)
    case false:
        self.blurView.resumeAnimation()

        UIView.animateWithDuration(0.1) {
            self.blurView.effect = nil
        }
    }
}

and the UIView extensions for pausing (with a delay) and resuming view's animation

extension UIView {

    public func pauseAnimation(delay delay: Double) {
        let time = delay + CFAbsoluteTimeGetCurrent()
        let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, time, 0, 0, 0, { timer in
            let layer = self.layer
            let pausedTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
            layer.speed = 0.0
            layer.timeOffset = pausedTime
        })
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
    }

    public func resumeAnimation() {
        let pausedTime  = layer.timeOffset

        layer.speed = 1.0
        layer.timeOffset = 0.0
        layer.beginTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
    }
}
mitja13
  • 791
  • 8
  • 6
  • 5
    For something Apple should have included in the first place, that is a very creative solution (especially as it uses no private APIs). – Karl White Jul 26 '16 at 18:47
  • This may work, but it's incomplete. Calling blurEffectView multiple times leads to multiple calls of pauseAnimation method, which adds a new timer on EVERY call and doesn't remove it. Moreover, if the current run loop already contains a runLoopTimer, CFRunLoopAddTimer will not add a new timer. – arturdev Aug 14 '19 at 19:59
  • Doesn't seem to work any more - or at least, not for me. I just simply get no blur effect whatsoever. For all of these solutions I either just get no blur at all, or I get the crash mentioned elsewhere here. – Dave Y May 04 '20 at 01:06
40

The reason you're getting heavy blur is that the blur effect style affects the brightness level of the image, not the amount of blur applied.

enter image description here

Unfortunately, although Apple clearly has the ability to control the amount of blur applied programmatically--try dragging down slowly on the launchpad to watch the Spotlight blurring transition--I don't see any public API to pass a blur amount to UIBlurEffect.

This post claims that adjusting the background color alpha will drive the blur amount. It's worth a try, but I don't see where that is documented: How to fade a UIVisualEffectView and/or UIBlurEffect in and out?

Community
  • 1
  • 1
StilesCrisis
  • 15,972
  • 4
  • 39
  • 62
  • 11
    Let me just recommend that you don't change the alpha values. As stated by Apple: "When using the UIVisualEffectView class, avoid alpha values that are less than 1.(...) UIVisualEffectView objects need to be combined as part of the content they are layered on top of in order to look correct. Setting the alpha to less than 1 on the visual effect view or any of its superviews causes many effects to look incorrect or not show up at all." Reference: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIVisualEffectView/ – tf.alves Nov 13 '15 at 20:37
  • hi @stilesCrisis Can you please help me out with this – Diksha Jun 22 '16 at 06:08
  • Just what i was looking for to understand why my blur wasn't a blur. Thanks ! – thibaut noah Aug 22 '17 at 15:34
  • Just looking into this and there are more blur styles. I've found .systemUltraThinMaterial to be a reasonably good effect: – P. Ent Apr 26 '21 at 19:35
  • Adjusting the alpha will just make it look like you lost one of your contact lenses. You will see the blurred image layered on top of the still sharp image behind it. I don't think that is what anyone are looking for. – Jesper Schläger Nov 17 '21 at 16:21
8

A solution similar to some here, but simpler, is to use a UIViewPropertyAnimator (iOS 10+) and set its fractionComplete property to some value between 0 and 1.

    // add blur view to image view
    let imgBlur = UIVisualEffectView()
    imgView.addSubview(imgBlur)
    imgBlur.frame = imgView.bounds

    // create animator to control blur strength
    let imgBlurAnimator = UIViewPropertyAnimator()
    imgBlurAnimator.addAnimations {
        imgBlur.effect = UIBlurEffect(style: .dark)
    }

    // 50% blur
    imgBlurAnimator.fractionComplete = 0.5

Note, if you plan to vary fractionComplete based on a pan gesture, scroll view, slider, etc. you'll want to set pausesOnCompletion = true (iOS 11+).

Devin Pitcher
  • 2,562
  • 1
  • 18
  • 11
  • 1
    i get this error "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'It is an error to release a paused or stopped property animator. Property animators must either finish animating or be explicitly stopped and finished before they can be released.'" – Peter Lapisu Jul 19 '18 at 12:42
  • needed to add [pa stopAnimation:YES]; [pa finishAnimationAtPosition:UIViewAnimatingPositionCurrent]; but it didnt changed the blur value – Peter Lapisu Jul 19 '18 at 12:45
  • Doesn't work on iOS 13 Beta when also setting imgBlurAnimator.stopAnimation(true). No bluring takeseffect. – Patrick Jun 15 '19 at 08:59
7

This works for me.

I put UIVisualEffectView in an UIView before add to my view.

I make this function to use easier. You can use this function to make blur any area in your view.

func addBlurArea(area: CGRect, style: UIBlurEffectStyle) {
    let effect = UIBlurEffect(style: style)
    let blurView = UIVisualEffectView(effect: effect)

    let container = UIView(frame: area)
    blurView.frame = CGRect(x: 0, y: 0, width: area.width, height: area.height)
    container.addSubview(blurView)
    container.alpha = 0.8
    self.view.insertSubview(container, atIndex: 1)
}

For example, you can make blur all of your view by calling:

addBlurArea(self.view.frame, style: UIBlurEffectStyle.Dark)

You can change Dark to your desired blur style and 0.8 to your desired alpha value

  • 8
    This removes the blur entirely. – djv Mar 22 '16 at 22:25
  • 1
    It still works for me now. You could check iOS version, XCode version... My current versions: iOS 9.2.1 (13D15) XCode 7.2 beta (7C62b) – Demilitarized Zone Mar 24 '16 at 16:22
  • 1
    I copied your code verbatim and it produces the same thing: a view with a partially opaque view with no blur, which I guess is technically "less blur". iOS 9.2 (13C75) Xcode 7.2.1 (7C1002) – djv Mar 24 '16 at 23:57
  • i realize, the view no longer remains blurred after i add blurView to the containerView, anything tht i'm missing? – user2525211 Aug 26 '18 at 12:41
  • Don't use alpha, If apple recommends not to do something it's worth a try not to do it. –  Dec 24 '18 at 05:08
4

You can add a UIBlurEffect over the image. And that will do the trick.

Here is an example of a UIImageView with blur effect on it. Remember to add a Image to the UIImageView.

Adjust blur amount with blurEffectView.alpha = 0.8 (from 0 to 1)

import UIKit

class BlurEffectImageView: UIImageView {

override func awakeFromNib() {
    super.awakeFromNib()
    addBlurEffect()
}

private func addBlurEffect() {
    let blurEffect = UIBlurEffect(style: .light)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    blurEffectView.alpha = 0.8
    
    blurEffectView.translatesAutoresizingMaskIntoConstraints = false
    addSubview(blurEffectView)
    
    NSLayoutConstraint(item: blurEffectView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0).isActive = true
    NSLayoutConstraint(item: blurEffectView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true
    NSLayoutConstraint(item: blurEffectView, attribute: .height,  relatedBy: .equal, toItem: self, attribute: .height,  multiplier: 1.0, constant: 0).isActive = true
    NSLayoutConstraint(item: blurEffectView, attribute: .width,   relatedBy: .equal, toItem: self, attribute: .width,   multiplier: 1.0, constant: 0).isActive = true
  }

}
alegelos
  • 2,308
  • 17
  • 26
  • This removes the blur effect entirely as well. – Jonathan Cabrera Oct 09 '18 at 20:51
  • I am not sure what u mean. The idea is just do add blur effect, what do u mean by "removes entirely"? – alegelos Apr 12 '19 at 08:47
  • @alegelos - It means that the image behind it is no longer blurred. I find the effect the same as if you used a standard UIView with say a black background with an alpha set. The content is visible but dimmed; however, it is not blurred. – C6Silver Nov 14 '19 at 07:26
4

Similar to @mitja13's solution, but uses UIViewPropertyAnimator, slightly more succinct:

var animator: UIViewPropertyAnimator!

viewDidLoad() {
   super.viewDidLoad()

   let blurEffectView = UIVisualEffectView()
   yourViewToBeBlurred.addSubview(blurEffectView)
   blurEffectView.fillSuperview() // This my custom method that anchors to the superview using auto layout. Write your own

   animator = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: {
       blurEffectView.effect = UIBlurEffect(style: .regular)
   })

   animator.fractionComplete = 0.6 // Adjust to the level of blur you want
 
}
Jack Guo
  • 3,959
  • 8
  • 39
  • 60
1

Add a BlurEffectView to a view with view's alpha < 1

func addBlurEffectView() -> Void {
    if !UIAccessibilityIsReduceTransparencyEnabled() {
        let viewContainer = UIView()
        viewContainer.frame = self.view.bounds
        viewContainer.alpha = 0.5

        let blurEffect = UIBlurEffect(style: .dark)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.layer.zPosition = -0.5;
        blurEffectView.frame = self.view.bounds;
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        viewContainer.addSubview(blurEffectView)

        self.view.addSubview(viewContainer)
        self.view.sendSubview(toBack: viewContainer)
    }
}
Tà Truhoada
  • 584
  • 7
  • 23
1

I use UIVisualEffectView like this to get adjustable blur circles. The blur level is controlled by a slider that controls the alpha. I'll include the slider handler below too. The blur circle size is adjustable with pinch spread action. I will include that too. And you can drag around the blur circles. I'll leave that as an exercise for the reader. If you want a blur rectangle, just don't round the corners. To see this blur circle design in action, load the MemeSoEasy app (free), add a photo (that you can put a blur circle on top of), then add a blur circle.

UIVisualEffectView *blurVisualEffectView;

UIVisualEffect *blurEffect;
blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
blurVisualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurVisualEffectView.frame = lastChosenBlurCircleRect;
blurVisualEffectView.center = CGPointMake(halfScreenX, halfScreenY);
[self.view addSubview:blurVisualEffectView];
CGFloat blurCornerRadius = blurVisualEffectView.bounds.size.width/2;
[[blurVisualEffectView layer]setCornerRadius:blurCornerRadius];
[[blurVisualEffectView layer]setMasksToBounds:YES];
[[blurVisualEffectView layer] setBorderWidth:4.0f];
[[blurVisualEffectView layer] setBorderColor:[UIColor blueColor].CGColor];
blurVisualEffectView.userInteractionEnabled = NO;
blurVisualEffectView.alpha = 0.97;
[blurArray addObject:blurVisualEffectView];

Slider handler :

Note that I store my blur objects in an array, so I can let users create as many as desired. The slider handler works on the last object in the array. The slider min and max values are 0.0 and 1.0

UISlider *slider_ = (UISlider *)sender;
CGFloat ourSliderValue = slider_.value;
UIVisualEffectView *currentBlurObject =
[blurArray objectAtIndex:blurArray.count - 1];
currentBlurObject.alpha = ourSliderValue;

Size change handler for pinch spread

int changeInWidth = 0; // one pixel at a time

if (pinchGesture.scale > 1.0) {
    changeInWidth++;
}
if (pinchGesture.scale < 1.0) {
    changeInWidth--;
}

UIVisualEffectView *currentBlurObject =
[blurArray objectAtIndex:blurArray.count - 1];

CGPoint oldCenter = currentBlurObject.center;

currentBlurObject.frame = CGRectMake(0, 0, currentBlurObject.frame.size.width + changeInWidth, currentBlurObject.frame.size.width + changeInWidth);

currentBlurObject.center = oldCenter;

lastChosenBlurCircleRect = currentBlurObject.frame;

CGFloat blurCornerRadius = currentBlurObject.frame.size.width/2;
[[currentBlurObject layer]setCornerRadius:blurCornerRadius];
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
user3408691
  • 93
  • 1
  • 1
  • 7
1

In order to use blur with level of blur - use my extension below:

public extension UIView {
  func applyBlur(level: CGFloat) {
    let context = CIContext(options: nil)
    self.makeBlurredImage(with: level, context: context, completed: { processedImage in
      let imageView = UIImageView(image: processedImage)
      imageView.translatesAutoresizingMaskIntoConstraints = false
      self.addSubview(imageView)
      NSLayoutConstraint.activate([
        imageView.topAnchor.constraint(equalTo: self.topAnchor),
        imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
        imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
        imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
      ])
    })
  }

  private func makeBlurredImage(with level: CGFloat, context: CIContext, completed: @escaping (UIImage) -> Void) {
    // screen shot
    UIGraphicsBeginImageContextWithOptions(self.frame.size, false, 1)
    self.layer.render(in: UIGraphicsGetCurrentContext()!)
    let resultImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    let beginImage = CIImage(image: resultImage)

    // make blur
    let blurFilter = CIFilter(name: "CIGaussianBlur")!
    blurFilter.setValue(beginImage, forKey: kCIInputImageKey)
    blurFilter.setValue(level, forKey: kCIInputRadiusKey)

    // extend source image na apply blur to it
    let cropFilter = CIFilter(name: "CICrop")!
    cropFilter.setValue(blurFilter.outputImage, forKey: kCIInputImageKey)
    cropFilter.setValue(CIVector(cgRect: beginImage!.extent), forKey: "inputRectangle")

    let output = cropFilter.outputImage
    var cgimg: CGImage?
    var extent: CGRect?

    let global = DispatchQueue.global(qos: .userInteractive)

    global.async {
      extent = output!.extent
      cgimg = context.createCGImage(output!, from: extent!)!
      let processedImage = UIImage(cgImage: cgimg!)

      DispatchQueue.main.async {
        completed(processedImage)
      }
    }
  }
}

How to use. Run it when frame if view already done. For example in viewDidAppear:

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

    myView.applyBlur(level: 5)
}
Leonif
  • 466
  • 4
  • 17
  • Nice answer. (voted.) One problem I see, though, is when the contents of the original image change. You don't have a way to remove and replace the old blur view. Perhaps use a tag on the blur view, and use that to find an remove any previous view before adding another one? Alternately you could attach the blur to the view as an associated value (although that's not very Swifty) – Duncan C Aug 13 '20 at 12:16
0

This answer is based on Mitja Semolic's excellent earlier answer. I've converted it to swift 3, added an explanation to what's happening in coments, made it an extension of a UIViewController so any VC can call it at will, added an unblurred view to show selective application, and added a completion block so that the calling view controller can do whatever it wants at the completion of the blur.

    import UIKit
//This extension implements a blur to the entire screen, puts up a HUD and then waits and dismisses the view.
    extension UIViewController {
        func blurAndShowHUD(duration: Double, message: String, completion: @escaping () -> Void) { //with completion block
            //1. Create the blur effect & the view it will occupy
            let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
            let blurEffectView = UIVisualEffectView()//(effect: blurEffect)
            blurEffectView.frame = self.view.bounds
            blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        //2. Add the effect view to the main view
            self.view.addSubview(blurEffectView)
        //3. Create the hud and add it to the main view
        let hud = HudView.getHUD(view: self.view, withMessage: message)
        self.view.addSubview(hud)
        //4. Begin applying the blur effect to the effect view
        UIView.animate(withDuration: 0.01, animations: {
            blurEffectView.effect = blurEffect
        })
        //5. Halt the blur effects application to achieve the desired blur radius
        self.view.pauseAnimationsInThisView(delay: 0.004)
        //6. Remove the view (& the HUD) after the completion of the duration
        DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
            blurEffectView.removeFromSuperview()
            hud.removeFromSuperview()
            self.view.resumeAnimationsInThisView()
            completion()
        }
    }
}

extension UIView {
    public func pauseAnimationsInThisView(delay: Double) {
        let time = delay + CFAbsoluteTimeGetCurrent()
        let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, time, 0, 0, 0, { timer in
            let layer = self.layer
            let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
            layer.speed = 0.0
            layer.timeOffset = pausedTime
        })
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
    }
    public func resumeAnimationsInThisView() {
        let pausedTime  = layer.timeOffset

        layer.speed = 1.0
        layer.timeOffset = 0.0
        layer.beginTime = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    }
}

I've confirmed that it works with both iOS 10.3.1 and iOS 11

James Jordan Taylor
  • 1,560
  • 3
  • 24
  • 38
0

HUGE THANKS TO mitja13, I make the Objective-C Version.

NS_ASSUME_NONNULL_BEGIN

@interface UIView (Gaoding)

- (void)gd_pauseAnimationsWithDelay:(double)delay;
- (void)gd_resumeAnimations;

@end

NS_ASSUME_NONNULL_END

@implementation UIView (Gaoding)

- (void)gd_pauseAnimationsWithDelay:(double)delay {
    double time = delay + CFAbsoluteTimeGetCurrent();
    __block CALayer *layer = self.layer;

    CFRunLoopRef runloopRef = CFRunLoopGetCurrent();
    CFRunLoopAddTimer(runloopRef, CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, time, 0, 0, 0, ^(CFRunLoopTimerRef timer) {
        double pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
        layer.speed = 0;
        layer.timeOffset = pausedTime;
        layer = nil;
        CFRunLoopRemoveTimer(runloopRef, timer, kCFRunLoopCommonModes);
        CFRelease(timer);
        timer = NULL;
    }), kCFRunLoopCommonModes);
}

- (void)gd_resumeAnimations {
    CALayer *layer = self.layer;
    double pausedTime = layer.timeOffset;
    layer.speed = 1;
    layer.timeOffset = 0.0;
    layer.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
}

@end

How to Use:

/// SHOW IT

UIVisualEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurEffectView = UIVisualEffectView.new;

// .... something other

[UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
    blurEffectView.effect = effect;
}];
[blurEffectView gd_pauseAnimationsWithDelay:0.1]; // 0.1/0.35 = 28.57% blur of UIBlurEffectStyleLight

// .... something other

/// HIDE IT
[blurEffectView gd_resumeAnimations];
[UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState animations:^{
    blurEffectView.effect = nil;
}];
Quanhua Guan
  • 445
  • 5
  • 9