30

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)
Christos Weip
  • 378
  • 1
  • 3
  • 9
  • Hey Christos. Any chance to review the answers and maybe there is more suitable answer to mark as solution? – bodich Sep 07 '22 at 07:56

6 Answers6

30

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)
    }
}
bodich
  • 1,708
  • 12
  • 31
  • 2
    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."` – Dave Y May 04 '20 at 00:13
  • 1
    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. – bodich May 05 '20 at 05:22
  • 1
    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. – TDesign Sep 14 '20 at 16:47
  • who voted it up? blur transparency is not managing by this code. – Aaban Tariq Murtaza Sep 02 '21 at 10:51
  • 1
    @AabanTariqMurtaza Yes, sure. Because the topic is about managing blur intensity, not transparency. – bodich Sep 02 '21 at 11:42
  • blur intensity is also not managing by this code. I tested with 0.1 & 1, and compared both snapshots. No difference found. – Aaban Tariq Murtaza Sep 02 '21 at 12:38
  • @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. – bodich Sep 02 '21 at 12:44
  • @AabanTariqMurtaza As I understand your blur is always strong like at 1.0? – bodich Sep 02 '21 at 12:45
  • Exactly. My blur is always strong like 1.0. – Aaban Tariq Murtaza Sep 02 '21 at 12:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236685/discussion-between-aaban-tariq-murtaza-and-bodich). – Aaban Tariq Murtaza Sep 02 '21 at 12:48
  • @AabanTariqMurtaza No any response from you in chat for almost 2 weeks. I guess you've solved the issue and everything works. – bodich Sep 14 '21 at 05:13
  • 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. – John Scalo Nov 14 '22 at 21:47
  • @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. – bodich Nov 16 '22 at 07:57
23

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.

pnizzle
  • 6,243
  • 4
  • 52
  • 81
  • please advise if you need swift alternatives to these suggestions. – pnizzle Jul 22 '15 at 05:19
  • 12
    Apple specifically advises against using alpha to control the effect of the blur. [UIVisualEffectView: Setting the Correct Alpha Value](https://developer.apple.com/reference/uikit/uivisualeffectview): _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._ – Axeva Feb 21 '17 at 19:37
  • 3
    You don’t control effect intensity by alpha at all. You just start to partially see completely sharp view under the effect. – bodich May 05 '20 at 05:31
  • 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. – pnizzle Aug 21 '20 at 00:28
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.

Tung Fam
  • 7,899
  • 4
  • 56
  • 63
2

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

Jinwoo Kim
  • 440
  • 4
  • 7
1

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

Christian
  • 22,585
  • 9
  • 80
  • 106
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
}
}
AndrewK
  • 907
  • 8
  • 14