67

I was wondering how to set the radius/blur factor of iOS new UIBlurEffectStyle.Light? I could not find anything in the documentation. But I want it to look similar to the classic UIImage+ImageEffects.h blur effect.

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    
    let blur = UIBlurEffect(style: UIBlurEffectStyle.Light)
    let effectView = UIVisualEffectView(effect: blur)
    effectView.frame = frame
    addSubview(effectView)
}
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
fabian
  • 5,433
  • 10
  • 61
  • 92

13 Answers13

74

Changing alpha is not a perfect solution. It does not affect blur intensity. You can setup an animation from nil to target blur effect and manually set time offset to get desired blur intensity. Unfortunately iOS will reset the animation offset when app returns from background.

Thankfully there is a simple solution that works on iOS >= 10. You can use UIViewPropertyAnimator. I didn't notice any issues with using it. I keeps custom blur intensity when app returns from background. Here is how you can implement it:

class CustomIntensityVisualEffectView: UIVisualEffectView {

    /// Create visual effect view with given effect and its intensity
    ///
    /// - Parameters:
    ///   - effect: visual effect, eg UIBlurEffect(style: .dark)
    ///   - intensity: custom intensity from 0.0 (no effect) to 1.0 (full effect) using linear scale
    init(effect: UIVisualEffect, intensity: CGFloat) {
        super.init(effect: nil)
        animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = effect }
        animator.fractionComplete = intensity
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    // MARK: Private
    private var animator: UIViewPropertyAnimator!

}

I also created a gist: https://gist.github.com/darrarski/29a2a4515508e385c90b3ffe6f975df7

Darrarski
  • 3,882
  • 6
  • 37
  • 59
  • 1
    This is be the accepted answer. Working like a charm! thanks – Rotem Aug 14 '18 at 08:38
  • 2
    Exploiting the fraction complete is such a smart solution. As far as I know, this is the only way to do a proper gradual blur using the UIBlurEffect. Changing the alpha of a blur view breaks the effect and SHOULD NOT be used. – DatForis Feb 27 '20 at 08:44
  • Works perfectly on iOS 13.4 too! Great job. – Kawe Apr 09 '20 at 13:18
  • Works great! For some reason whenever I try and do this manually myself I always get a crash to do with the animation being released. Anyway - this would be even better if there was a way of slowing down the transition! Any ideas on how to do that? – Dave Y May 18 '20 at 01:09
  • Just a follow-up to my comment: I've found a (relatively dodgy) way of doing this by implementing the Interpolate library. This doesn't seem to work with UIViewPropertyAnimators for some reason. Just add an extra argument to the init for duration, then replace 'animator.fractionComplete = intensity' with the following: `let interpolateAnimation = Interpolate(from: 0, to: intensity) { (destination) in self.animator.fractionComplete = CGFloat(destination) print(self.animator.fractionComplete) } interpolateAnimation.animate(duration: duration)` – Dave Y May 18 '20 at 01:26
  • I've used this method in `viewDidLoad`, a key factor to make it work is to keep a `strong` reference to the `animator`. – Shebuka Oct 23 '20 at 15:16
  • 1
    Add `animator.pausesOnCompletion = true` to get consistent results. Otherwise the animation could get reset. – Hans Bondoka Aug 18 '22 at 18:47
28

You can change the alpha of the UIVisualEffectView that you add your blur effect to.

let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.alpha = 0.5
blurEffectView.frame = self.view.bounds
self.view.addSubview(blurEffectView)

This is not a true solution, as it doesn't actually change the radius of the blur, but I have found that it gets the job done with very little work.

Kwalker108
  • 472
  • 4
  • 9
  • While not addressing the core problem, this is certainly a very nice workaround and more likely to be accepted into the store! Nice one. – ColinMasters Oct 10 '15 at 14:54
  • 25
    When using the UIVisualEffectView class, avoid alpha values that are less than 1. Creating views that are partially transparent causes the system to combine the view and all the associated subviews during an offscreen render pass. 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. https://developer.apple.com/library/tvos/documentation/UIKit/Reference/UIVisualEffectView/index.html – sabalaba Jan 09 '16 at 20:56
21

Although it is a hack and probably it won't be accepted in the app store, it is still possible. You have to subclass the UIBlurEffect like this:

#import <objc/runtime.h>

@interface UIBlurEffect (Protected)
@property (nonatomic, readonly) id effectSettings;
@end

@interface MyBlurEffect : UIBlurEffect
@end

@implementation MyBlurEffect

+ (instancetype)effectWithStyle:(UIBlurEffectStyle)style
{
    id result = [super effectWithStyle:style];
    object_setClass(result, self);

    return result;
}

- (id)effectSettings
{
    id settings = [super effectSettings];
    [settings setValue:@50 forKey:@"blurRadius"];
    return settings;
}

- (id)copyWithZone:(NSZone*)zone
{
    id result = [super copyWithZone:zone];
    object_setClass(result, [self class]);
    return result;
}

@end

Here blur radius is set to 50. You can change 50 to any value you need.

Then just use MyBlurEffect class instead of UIBlurEffect when creating your effect for UIVisualEffectView.

Eugene Dudnyk
  • 5,553
  • 1
  • 23
  • 48
  • 1
    I swear they announced at the platforms state of the union that you can modify the blurRadius! But its not in any docs that I could find! – Andrew Jul 05 '15 at 19:16
  • This sholution doesn't work. After changing blurRadius error occures: `malloc: *** error for object 0x7fbf9969b690: incorrect checksum for freed object - object was probably modified after being freed. *** set a breakpoint in malloc_error_break to debug` – Sound Blaster Jul 21 '15 at 12:10
  • 1
    Works perfectly if to do it exactly how it is written in the answer. If you modify blur radius after adding the BlurEffect onto the Effect View - you are doing wrong. The BlurEffect is copied during adding, and the instance you are modifying is not used by an EffectView. – Eugene Dudnyk Jul 21 '15 at 12:18
  • Is only one take this answer and accepted in the app store? – mayqiyue Apr 11 '16 at 10:17
  • this works, but it doesn't show gradual changes in blur radius going - let's say - from radius 1 to 5 or 10... (at least in the simulator) – Edoardo Sep 10 '16 at 14:56
  • Do you mean you want to animate radius change? – Eugene Dudnyk Sep 10 '16 at 18:12
  • no, I don't mean animation, just noticed that the blur doesn't seems to be applied linearly with the set value. Example: when I apply radius 2 it doesn't look visually different than when I apply a radius 10 or so. – Edoardo Sep 12 '16 at 10:19
18

Recently developed Bluuur library to dynamically change blur radius of UIVisualEffectsView without usage any of private APIs: https://github.com/ML-Works/Bluuur

It uses paused animation of setting effect to achieve changing radius of blur. Solution based on this gist: https://gist.github.com/n00neimp0rtant/27829d87118d984232a4

And the main idea is:

// Freeze animation
blurView.layer.speed = 0;

blurView.effect = nil;
[UIView animateWithDuration:1.0 animations:^{
    blurView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
}];

// Set animation progress from 0 to 1
blurView.layer.timeOffset = 0.5;

UPDATE:

Apple introduced UIViewPropertyAnimator class in iOS 10. Thats what we need exactly to animate .effect property of UIVisualEffectsView. Hope community will be able to back-port this functionality to previous iOS version.

k06a
  • 17,755
  • 10
  • 70
  • 110
  • 2
    This should really be the accepted answer. Currently accepted one goes directly against [Apple's guidelines](https://developer.apple.com/reference/uikit/uivisualeffectview). – tomas789 Mar 28 '17 at 12:54
  • 2
    Since December 2016 and the commit https://github.com/ML-Works/Bluuur/commit/bf531b081bb10d2c76b9fe44411953ee54074b66 the Bluuur project **does** use private API to change the blurRadius! See this issue: https://github.com/ML-Works/Bluuur/issues/17 – void256 May 18 '17 at 13:57
  • @void256 thanks! This approach works more stable especially with animations. – k06a May 18 '17 at 14:03
6

This is totally doable. Use CIFilter in CoreImage module to customize blur radius. In fact, you can even achieve a blur effect with continuous varying (aka gradient) blur radius (https://stackoverflow.com/a/51603339/3808183)

import CoreImage

let ciContext = CIContext(options: nil)

guard let inputImage = CIImage(image: yourUIImage), 
    let mask = CIFilter(name: "CIGaussianBlur") else { return }
mask.setValue(inputImage, forKey: kCIInputImageKey)
mask.setValue(10, forKey: kCIInputRadiusKey) // Set your blur radius here

guard let output = mask.outputImage,
    let cgImage = ciContext.createCGImage(output, from: inputImage.extent) else { return }
outUIImage = UIImage(cgImage: cgImage)
Jack Guo
  • 3,959
  • 8
  • 39
  • 60
3

I'm afraid there's no such api currently. According to Apple's way of doing things, new functionality was always brought with restricts, and capabilities will bring out gradually. Maybe that will be possible on iOS 9 or maybe 10...

Louis Zhu
  • 792
  • 5
  • 12
3

I have ultimate solution for this question:

fileprivate final class UIVisualEffectViewInterface {
func setIntensity(effectView: UIVisualEffectView, intensity: CGFloat){
    let effect = effectView.effect
    effectView.effect = nil
    animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [weak effectView] in effectView?.effect = effect }
    animator.fractionComplete = intensity
}

private var animator: UIViewPropertyAnimator! }



extension UIVisualEffectView{
private var key: UnsafeRawPointer? { UnsafeRawPointer(bitPattern: 16) }

private var interface: UIVisualEffectViewInterface{
    if let key = key, let visualEffectViewInterface = objc_getAssociatedObject(self, key) as? UIVisualEffectViewInterface{
        return visualEffectViewInterface
    }
    let visualEffectViewInterface = UIVisualEffectViewInterface()
    
    if let key = key{
        objc_setAssociatedObject(self, key, visualEffectViewInterface, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }
    return visualEffectViewInterface
}

func intensity(_ value: CGFloat){
    interface.setIntensity(effectView: self, intensity: value)
}}
1

This idea hits me after tried the above solutions, a little hacky but I got it working. Since we cannot modify the default radius which is set as "50", we can just enlarge it and scale it back down.

previewView.snp.makeConstraints { (make) in
    make.centerX.centerY.equalTo(self.view)
    make.width.height.equalTo(self.view).multipliedBy(4)
}

previewBlur.snp.makeConstraints { (make) in
    make.edges.equalTo(previewView)
}

And then,

previewView.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)
previewBlur.transform = CGAffineTransform(scaleX: 0.25, y: 0.25)

I got a 12.5 blur radius. Hope this will help :-)

Yansong
  • 11
  • 2
0

Currently I didn't find any solution.
By the way you can add a little hack in order to let blur mask less "blurry", in this way:

let blurView = .. // here create blur view as usually

if let blurSubviews = self.blurView?.subviews {
    for subview in blurSubviews {
        if let filterView = NSClassFromString("_UIVisualEffectFilterView") {
            if subview.isKindOfClass(filterView) {
                subview.hidden = true
            }
        }
    }
}
Luca Davanzo
  • 21,000
  • 15
  • 120
  • 146
0

for iOS 11.*

in viewDidLoad()

    let blurEffect = UIBlurEffect(style: .dark)
    let blurEffectView = UIVisualEffectView()
    view.addSubview(blurEffectView)

    //always fill the view
    blurEffectView.frame = self.view.bounds
    blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    UIView.animate(withDuration: 1) {
        blurEffectView.effect = blurEffect
    }
    blurEffectView.pauseAnimation(delay: 0.5)
Siempay
  • 876
  • 1
  • 11
  • 32
0

There is an undocumented way to do this. Not necessarily recommended, as it may get your app rejected by Apple. But it does work.

if let blurEffectType = NSClassFromString("_UICustomBlurEffect") as? UIBlurEffect.Type {
        let blurEffectInstance = blurEffectType.init()

        // set any value you want here. 40 is quite blurred 
        blurEffectInstance.setValue(40, forKey: "blurRadius")
        let effectView: UIVisualEffectView = UIVisualEffectView(effect: blurEffectInstance)

        // Now you have your blurred visual effect view
    }
jjjjjjjj
  • 4,203
  • 11
  • 53
  • 72
-2

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) {
    let effect = UIBlurEffect(style: UIBlurEffectStyle.Dark)
    let blurView = UIVisualEffectView(effect: effect)
    blurView.frame = CGRect(x: 0, y: 0, width: area.width, height: area.height)

    let container = UIView(frame: area)
    container.alpha = 0.8
    container.addSubview(blurView)

    self.view.insertSubview(container, atIndex: 1)
}

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

addBlurArea(self.view.frame)

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

-6

If you want to accomplish the same behaviour as iOS spotlight search you just need to change the alpha value of the UIVisualEffectView (tested on iOS9 simulator)

Serg Dort
  • 477
  • 2
  • 5