How to edit the UIBlurEffect intensity?
Asked Answered
C

6

32

I don't want my background image to be too blury. Isn't there a property to adjust the blur intensity?

let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
blurEffect.???
let effectView = UIVisualEffectView(effect: blurEffect)
effectView.frame = backgroundAlbumCover.bounds
backgroundAlbumCover.addSubview(effectView)
Clari answered 25/1, 2015 at 19:52 Comment(1)
Hey Christos. Any chance to review the answers and maybe there is more suitable answer to mark as solution?Laurettelauri
K
24

Adjusting the blur itself is not possible... But, you can adjust how visible the blur view is. This can be done in a number of ways, only three of which I can think of at the moment:

1st Option: Adjust the alpha of your UIVisualEffectView instance e.g:

effectView.alpha = 0.4f;

2nd Option: Add a UIView instance to effectView at Index 0 and adjust the alpha of this UIView instance. e.g:

UIView *blurDilutionView = [[UIView alloc] initWithFrame: effectView.frame];
blurDilutionView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent: 0.5];
blurDilutionView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;//if not using AutoLayout
[effectView insertSubview:blurDilutionView atIndex:0];

3rd Option: use multiple UIVisualEffectView instances (I have not tried this yet, more of an idea). Apply an alpha of 0.1f on each. The more UIVisualEffectView views you have the more blurry the overall look. Once again, I have not tried this option yet!

Update: As Axeva mentioned in the comments, Apple advises against adjusting the alpha to change the blur. So use these suggestions at your own potential peril.

Kindig answered 22/7, 2015 at 5:18 Comment(4)
please advise if you need swift alternatives to these suggestions.Kindig
Apple specifically advises against using alpha to control the effect of the blur. UIVisualEffectView: Setting the Correct Alpha Value: When using the UIVisualEffectView class, avoid alpha values that are less than 1. … 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.Bon
You don’t control effect intensity by alpha at all. You just start to partially see completely sharp view under the effect.Laurettelauri
Yes these are 'solutions' that are advised against by Apple. They are however not completely forbidden and for some people the resulting incorrect look is what they are after. Not all apps are destined for the public AppStore. Some are for personal and private distribution purposes. We therefore cannot choose not to think of a solution just because Apple recommends against it.Kindig
L
32

You can do that in super elegant way with animator

(reducing UIVisualEffectView alpha will not affect blur intensity, so we must use animator)

Usage as simple as:

let blurEffectView = BlurEffectView()
view.addSubview(blurEffectView)

BlurEffectView realisation:

class BlurEffectView: UIVisualEffectView {
    
    var animator = UIViewPropertyAnimator(duration: 1, curve: .linear)
    
    override func didMoveToSuperview() {
        guard let superview = superview else { return }
        backgroundColor = .clear
        frame = superview.bounds //Or setup constraints instead
        setupBlur()
    }
    
    private func setupBlur() {
        animator.stopAnimation(true)
        effect = nil

        animator.addAnimations { [weak self] in
            self?.effect = UIBlurEffect(style: .dark)
        }
        animator.fractionComplete = 0.1   //This is your blur intensity in range 0 - 1
    }
    
    deinit {
        animator.stopAnimation(true)
    }
}
Laurettelauri answered 10/2, 2020 at 18:53 Comment(13)
Seems like a great idea, but I can't get it to work on my project over here. I get a crash each time the relevant view loads. Thread 1: Exception: "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."Antivenin
Dave, seems like you did not stop it before it was released as message says. Maybe class where you’ve add animator was deinited. Try to add deinit explicitly and check. You have not a blur problem, you are using animator in incorrect way somewhere.Laurettelauri
The reason you are getting the crash is because your code is trying to get rid of the animator. Declare it on top of the class, just so it has the same timespan as the class.Nies
who voted it up? blur transparency is not managing by this code.Board
@AabanTariqMurtaza Yes, sure. Because the topic is about managing blur intensity, not transparency.Laurettelauri
blur intensity is also not managing by this code. I tested with 0.1 & 1, and compared both snapshots. No difference found.Board
@AabanTariqMurtaza Intensity works great on my side in 2 projects, and for other people. You can share the simple sample project here (for example github) with only 2 views (one under another) and I'll take a look why you don't get the working intensity.Laurettelauri
@AabanTariqMurtaza As I understand your blur is always strong like at 1.0?Laurettelauri
Exactly. My blur is always strong like 1.0.Board
Let us continue this discussion in chat.Board
@AabanTariqMurtaza No any response from you in chat for almost 2 weeks. I guess you've solved the issue and everything works.Laurettelauri
This seemed promising at first but in my experience when the app backgrounds and then comes back to the foreground, iOS fast forwards the outstanding animation to 1.0 fractionComplete and the view suddenly appears with full blur.Laxative
@JohnScalo You might try investigate view/viewcontroller lifecycle. I don’t have such problem, so I think somewhere unwanted updates occur like layouting etc. it can be called on any link in a responder chain. I use this blur in 2 projects and it keeps working well with your case.Laurettelauri
K
24

Adjusting the blur itself is not possible... But, you can adjust how visible the blur view is. This can be done in a number of ways, only three of which I can think of at the moment:

1st Option: Adjust the alpha of your UIVisualEffectView instance e.g:

effectView.alpha = 0.4f;

2nd Option: Add a UIView instance to effectView at Index 0 and adjust the alpha of this UIView instance. e.g:

UIView *blurDilutionView = [[UIView alloc] initWithFrame: effectView.frame];
blurDilutionView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent: 0.5];
blurDilutionView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;//if not using AutoLayout
[effectView insertSubview:blurDilutionView atIndex:0];

3rd Option: use multiple UIVisualEffectView instances (I have not tried this yet, more of an idea). Apply an alpha of 0.1f on each. The more UIVisualEffectView views you have the more blurry the overall look. Once again, I have not tried this option yet!

Update: As Axeva mentioned in the comments, Apple advises against adjusting the alpha to change the blur. So use these suggestions at your own potential peril.

Kindig answered 22/7, 2015 at 5:18 Comment(4)
please advise if you need swift alternatives to these suggestions.Kindig
Apple specifically advises against using alpha to control the effect of the blur. UIVisualEffectView: Setting the Correct Alpha Value: When using the UIVisualEffectView class, avoid alpha values that are less than 1. … 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.Bon
You don’t control effect intensity by alpha at all. You just start to partially see completely sharp view under the effect.Laurettelauri
Yes these are 'solutions' that are advised against by Apple. They are however not completely forbidden and for some people the resulting incorrect look is what they are after. Not all apps are destined for the public AppStore. Some are for personal and private distribution purposes. We therefore cannot choose not to think of a solution just because Apple recommends against it.Kindig
C
7

Once I ran into a problem to create a blur effect that is darker than .light and lighter than .dark UIBlurEffect style.

To achieve that, put a view on the back with the color and alpha you need:

    let pictureImageView = // Image that you need to blur
    let backView = UIView(frame: pictureImageView.bounds)
    backView.backgroundColor = UIColor(red: 100/255, green: 100/255, blue: 100/255, alpha: 0.3)
    pictureImageView.addSubview(backView)

    let blurEffect = UIBlurEffect(style: .light)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    blurEffectView.frame = pictureImageView.bounds
    pictureImageView.addSubview(blurEffectView)

How the result looks like:

enter image description here

For more details, check out this article.

UPDATE: apparently there is another nice (maybe even nicer) way to implement the Blur using CIFilter(name: "CIGaussianBlur"). It allows the make “opacity” and blur’s strengths much lower than UIBlurEffect.

Corrie answered 1/7, 2018 at 13:54 Comment(1)
CIFilter(name: "CIGaussianBLur") can be applied to UIImages, but cannot be applied to CALayers or other Layer objects' backgroundFilters property. The docs for that property say the following: "This property is not supported on layers in iOS."Straddle
P
4

Use Private API if you want. Tested on iOS 13.7, 14.8, 15.5, 16.0. Does not work with Mac Catalyst.

Sample

  • UIVisualEffectView+Intensity.h
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIVisualEffectView (Intensity)
@property (nonatomic) CGFloat intensity;
@end

NS_ASSUME_NONNULL_END
  • UIVisualEffectView+Intensity.m
#import "UIVisualEffectView+Intensity.h"
#import <objc/message.h>

@interface UIVisualEffectView (Intensity)
@property (readonly) id backgroundHost; // _UIVisualEffectHost
@property (readonly) __kindof UIView *backdropView; // _UIVisualEffectBackdropView
@end

@implementation UIVisualEffectView (Intensity)

- (id)backgroundHost {
    id backgroundHost = ((id (*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(@"_backgroundHost")); // _UIVisualEffectHost
    return backgroundHost;
}

- (__kindof UIView * _Nullable)backdropView {
    __kindof UIView *backdropView = ((__kindof UIView * (*)(id, SEL))objc_msgSend)(self.backgroundHost, NSSelectorFromString(@"contentView")); // _UIVisualEffectBackdropView
    return backdropView;
}

- (CGFloat)intensity {
    __kindof UIView *backdropView = self.backdropView; // _UIVisualEffectBackdropView
    __kindof CALayer *backdropLayer = ((__kindof CALayer * (*)(id, SEL))objc_msgSend)(backdropView, NSSelectorFromString(@"backdropLayer")); // UICABackdropLayer
    
    NSArray *filters = backdropLayer.filters;
    id _Nullable __block gaussianBlur = nil; // CAFilter
    
    [filters enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (![obj respondsToSelector:NSSelectorFromString(@"type")]) return;
        
        NSString *type = ((NSString * (*)(id, SEL))objc_msgSend)(obj, NSSelectorFromString(@"type"));
        
        if (![type isKindOfClass:[NSString class]]) return;
        
        if ([type isEqualToString:@"gaussianBlur"]) {
            gaussianBlur = obj;
            *stop = YES;
        }
    }];
    
    if (gaussianBlur == nil) return 0.0f;
    
    NSNumber * _Nullable inputRadius = [gaussianBlur valueForKeyPath:@"inputRadius"];
    
    if ((inputRadius == nil) || (![inputRadius isKindOfClass:[NSNumber class]])) return 0.0f;
    
    return [inputRadius floatValue];
}

- (void)setIntensity:(CGFloat)intensity {
    id descriptor = ((id (*)(id, SEL, id, BOOL))objc_msgSend)(self, NSSelectorFromString(@"_effectDescriptorForEffects:usage:"), @[self.effect], YES); // _UIVisualEffectDescriptor
    
    NSArray *filterEntries = ((NSArray * (*)(id, SEL))objc_msgSend)(descriptor, NSSelectorFromString(@"filterEntries")); // NSArray<_UIVisualEffectFilterEntry *>
    
    id _Nullable __block gaussianBlur = nil; // _UIVisualEffectFilterEntry
    
    [filterEntries enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSString *filterType = ((NSString * (*)(id, SEL))objc_msgSend)(obj, NSSelectorFromString(@"filterType"));
        
        if ([filterType isEqualToString:@"gaussianBlur"]) {
            gaussianBlur = obj;
            *stop = YES;
        }
    }];
    
    if (gaussianBlur == nil) return;
    
    NSMutableDictionary *requestedValues = [((NSDictionary * (*)(id, SEL))objc_msgSend)(gaussianBlur, NSSelectorFromString(@"requestedValues")) mutableCopy];

    if (![requestedValues.allKeys containsObject:@"inputRadius"]) {
        NSLog(@"Not supported effect.");
        return;
    }

    requestedValues[@"inputRadius"] = [NSNumber numberWithFloat:intensity];

    ((void (*)(id, SEL, NSDictionary *))objc_msgSend)(gaussianBlur, NSSelectorFromString(@"setRequestedValues:"), requestedValues);
    
    ((void (*)(id, SEL, id))objc_msgSend)(self.backgroundHost, NSSelectorFromString(@"setCurrentEffectDescriptor:"), descriptor);
    
    ((void (*)(id, SEL))objc_msgSend)(self.backdropView, NSSelectorFromString(@"applyRequestedFilterEffects"));
}

@end
  • Usage
let firstBlurView: UIVisualEffectView = .init(effect: UIBlurEffect(style: .dark))

// setter
firstBlurView.intensity = 7

// getter
print(firstBlurView.intensity) // 7.0

Particularity answered 17/8, 2022 at 6:15 Comment(3)
Amazing! Thank you so much for making this! ❤️Sac
could this extension available for Swift?Salmons
Is it possible to recreate this in Swift?Laurettelauri
S
1

UIBlurEffect doesn't provide such a property. If you want another intensity, you will have to make a BlurEffect by yourself.

Stallings answered 25/1, 2015 at 20:20 Comment(2)
Any idea on how to do this, Christian?Barracuda
@Barracuda please see the suggestions I have posted. May be late but hopefully should help othersKindig
I
1

Here is the BlurEffectView class with public intensity setter as well as with conformance to Apple's UIView.animation functions (you can animate intensity by UIKit's animations)

BlurEffectView.swift

import UIKit

public class BlurEffectView: UIView {

public override class var layerClass: AnyClass {
    return BlurIntensityLayer.self
}

@objc
@IBInspectable
public dynamic var intensity: CGFloat {
    set { self.blurIntensityLayer.intensity = newValue }
    get { return self.blurIntensityLayer.intensity }
}
@IBInspectable
public var effect = UIBlurEffect(style: .dark) {
    didSet {
        self.setupPropertyAnimator()
    }
}
private let visualEffectView = UIVisualEffectView(effect: nil)
private var propertyAnimator: UIViewPropertyAnimator!
private var blurIntensityLayer: BlurIntensityLayer {
    return self.layer as! BlurIntensityLayer
}

public override init(frame: CGRect) {
    super.init(frame: frame)
    self.setupView()
}

public required init?(coder: NSCoder) {
    super.init(coder: coder)
    self.setupView()
}

deinit {
    self.propertyAnimator.stopAnimation(true)
}

private func setupPropertyAnimator() {
    self.propertyAnimator?.stopAnimation(true)
    self.propertyAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
    self.propertyAnimator.addAnimations { [weak self] in
        self?.visualEffectView.effect = self?.effect
    }
    self.propertyAnimator.pausesOnCompletion = true
}
  
private func setupView() {
    self.backgroundColor = .clear
    self.isUserInteractionEnabled = false
    
    self.addSubview(self.visualEffectView)
    self.visualEffectView.fill(view: self)
    self.setupPropertyAnimator()
}

public override func display(_ layer: CALayer) {
    guard let presentationLayer = layer.presentation() as? BlurIntensityLayer else {
        return
    }
    let clampedIntensity = max(0.0, min(1.0, presentationLayer.intensity))
    self.propertyAnimator.fractionComplete = clampedIntensity
}
}

BlurIntensityLayer.swift

import QuartzCore

class BlurIntensityLayer: CALayer {

@NSManaged var intensity: CGFloat

override init(layer: Any) {
    super.init(layer: layer)
    
    if let layer = layer as? BlurIntensityLayer {
        self.intensity = layer.intensity
    }
}

override init() {
    super.init()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override class func needsDisplay(forKey key: String) -> Bool {
    key == #keyPath(intensity) ? true : super.needsDisplay(forKey: key)
}

override func action(forKey event: String) -> CAAction? {
    guard event == #keyPath(intensity) else {
        return super.action(forKey: event)
    }
    
    let animation = CABasicAnimation(keyPath: event)
    animation.toValue = nil
    animation.fromValue = (self.presentation() ?? self).intensity
    return animation
}
}
Improbable answered 14/12, 2021 at 11:13 Comment(1)
This is by far the only one that worked (and with @IBInspectable!)Elsy

© 2022 - 2024 — McMap. All rights reserved.