Less Blur with `Visual Effect View with Blur`?
Asked Answered
C

11

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.

Collocation answered 7/4, 2015 at 18:44 Comment(0)
S
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?

Suffragan answered 7/4, 2015 at 18:58 Comment(5)
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: developer.apple.com/library/ios/documentation/UIKit/Reference/…Mcgowan
hi @stilesCrisis Can you please help me out with thisJunkman
Just what i was looking for to understand why my blur wasn't a blur. Thanks !Earth
Just looking into this and there are more blur styles. I've found .systemUltraThinMaterial to be a reasonably good effect:Gastight
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.Procuration
H
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
    }
}
Heist answered 31/5, 2016 at 22:12 Comment(3)
For something Apple should have included in the first place, that is a very creative solution (especially as it uses no private APIs).Midyear
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.Wingspread
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.Statvolt
S
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?

Suffragan answered 7/4, 2015 at 18:58 Comment(5)
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: developer.apple.com/library/ios/documentation/UIKit/Reference/…Mcgowan
hi @stilesCrisis Can you please help me out with thisJunkman
Just what i was looking for to understand why my blur wasn't a blur. Thanks !Earth
Just looking into this and there are more blur styles. I've found .systemUltraThinMaterial to be a reasonably good effect:Gastight
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.Procuration
B
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+).

Benedic answered 16/3, 2018 at 0:48 Comment(3)
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.'"Leek
needed to add [pa stopAnimation:YES]; [pa finishAnimationAtPosition:UIViewAnimatingPositionCurrent]; but it didnt changed the blur valueLeek
Doesn't work on iOS 13 Beta when also setting imgBlurAnimator.stopAnimation(true). No bluring takeseffect.Hoyle
G
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

Glittery answered 29/10, 2015 at 23:17 Comment(5)
This removes the blur entirely.Agamemnon
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)Glittery
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)Agamemnon
i realize, the view no longer remains blurred after i add blurView to the containerView, anything tht i'm missing?Foulk
Don't use alpha, If apple recommends not to do something it's worth a try not to do it.Dramatist
G
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
  }

}
Gunsel answered 8/6, 2017 at 19:17 Comment(3)
This removes the blur effect entirely as well.Pettit
I am not sure what u mean. The idea is just do add blur effect, what do u mean by "removes entirely"?Gunsel
@Gunsel - 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.Interlunation
C
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
 
}
Ceruse answered 3/7, 2020 at 3:13 Comment(1)
I only tested it just for the sake of testing. Nice & clean solution. Thanks. Up VotedContradistinction
J
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)
    }
}
Joanajoane answered 29/9, 2017 at 5:26 Comment(0)
P
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];
Premarital answered 30/11, 2018 at 7:3 Comment(0)
G
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)
}
Gurevich answered 10/6, 2019 at 6:32 Comment(1)
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)Wound
F
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

Featured answered 18/10, 2017 at 15:29 Comment(0)
I
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;
}];
Innards answered 28/2, 2020 at 8:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.