21

Apple provides this live blurring view class UIVisualEffectView and you use it with a UIBlurEffect which takes one of three available UIBlurEffectStyles:

enum UIBlurEffectStyle : Int {
    case ExtraLight
    case Light
    case Dark
}

Now for demo purposes I set up two effect views with the styles Light and ExtraLight:

let lightBlur = UIBlurEffect(style: UIBlurEffectStyle.Light)
let extraLightBlur = UIBlurEffect(style: UIBlurEffectStyle.ExtraLight)

let lightView = UIVisualEffectView(effect: lightBlur)
lightView.frame = CGRectMake(10, 30, 150, 150)
self.view.addSubview(lightView)

let extraLightView = UIVisualEffectView(effect: extraLightBlur)
extraLightView.frame = CGRectMake(160, 30, 150, 150)
self.view.addSubview(extraLightView)

So far so good, everything works as expected, they blur images:

image

and kind of work on colors, too:

black red

but when it comes to a white background this happens:

white

The Light effect on the left works as expected, but the ExtraLight effect on the right leaves some kind of gray square behind.

Now the question: Is there any kind of trick or method that would enable me to use an extra light blur effect on white background (with live blurring support) AND remove that ugly gray shadow?

julia_v
  • 593
  • 3
  • 18
Finn Gaida
  • 4,000
  • 4
  • 20
  • 30

9 Answers9

16

As far as I know the additional tint is a built-in feature of the UIVisualEffectView class. If you examine the view hierarchy with Xcode, you can see that there are two default subviews in the visual effect view instance: UIVisualEffectBackdropView and UIVisualEffectSubview. (I assume that these are private classes.) If you inspect UIVisualEffectSubview you can see that it has a background color which causes the unwanted tint that you've noticed.

I am not sure if there's an officially supported way to remove it, but you can modify this background color by filtering to the name of the private subview:

if let vfxSubView = visualEffectView.subviews.first(where: {
    String(describing: type(of: $0)) == "_UIVisualEffectSubview"
}) {
    vfxSubView.backgroundColor = UIColor.white.withAlphaComponent(0.7)
}

(Swift 5 compatible)

The default tint is around 70% opacity, so the easiest is to use the target background color with 0.7 alpha component.

I've also noticed that this might reset to the default value if the visual effect contains a custom subview. If I add this same snippet to the viewDidLayoutSubviews function of the view's controller, then it will keep the custom background color even after the built-in subview is updated.

Here's an example with a dark blur effect style. The top part shows the default tint and the bottom version has a custom black background color with 70% opacity.

enter image description here

Endanke
  • 867
  • 13
  • 24
  • 3
    I don't particularly like manipulating the hidden iOS subviews, but this method seems safe enough and it actually works! – hellaandrew Aug 10 '20 at 18:41
  • Is there a chance the app gets rejected by Apple if using the first solution ? This is now a "private" API I guess ?.. – Jerem Lachkar Dec 05 '20 at 20:22
  • 2
    @JeremLachkar I don't think they would reject it. Yes the UIVisualEffectSubview is a private type, but this snippet doesn't reference that symbol, it only checks the string description of the view and I doubt that Apple is considering those in the review process. Also I've delivered a couple apps with this code without rejection. – Endanke Feb 05 '21 at 15:29
  • Do you think this will work for macOS as well? Thanks – Sentry.co Jul 29 '21 at 10:49
3

If you just want the blur and your blurred view is gonna be stationary, you could use the UIImageEffects class and change the tintColor to a "full" white:

- (UIImage *)applyExtraLightEffect
{
    UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82];
    return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}

As far as I know you can't change it in the UIVisualEffectView.

Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130
lennartk
  • 570
  • 1
  • 4
  • 15
  • Yes, that resolves the gray shadow problem, but unfortunately this method is not fast enough to create a smooth live blur when I scroll stuff behind the blur view... – Finn Gaida Jul 22 '16 at 09:45
3

You can try :

var visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))    
visualEffectView.frame = imageView.bounds
imageView.addSubview(visualEffectView)
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
2

The following code should do the trick to give it a the desired tint:

let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
visualEffectView.subviews.forEach { subview in
    subview.backgroundColor = UIColor.blue.withAlphaComponent(0.2)
}

Important Note:

I wouldn't recommend adding a check for "_UIVisualEffectSubview" since this class can change on subsequent iOS updates. Also, there is a possibility of app getting rejected because of this.

pkamb
  • 33,281
  • 23
  • 160
  • 191
atulkhatri
  • 10,896
  • 3
  • 53
  • 89
1

iOS 15 has the nice

.background(.ultraThinMaterial)

but it still suffers from the nasty grey tint. Wish there was an option to blur background views without any tint.

I tried a simple color-correct hack which worked for my background color but only in light mode! For dark mode I just made the background solid black (no translucent blur)

.background(
    // negate the slight grey tint of ultrathinmaterial
    Color("materialTintColorCorrect")
        .background(.ultraThinMaterial)
)

My "materialTintColorCorrect" color asset was #F0F8FF 27% opacity for light mode and #000000 100% opacity for dark.

Bruce Webster
  • 171
  • 10
0

Simple Solution

I could find a simple solution inspired by Shannon Hughes' Blog Post. Just add a white background color with transparency to the effect view. I don't know if it is exactly like extraLight but for me it is close enough.

let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
visualEffectView.frame = sectionHeaderView.bounds
visualEffectView.backgroundColor = UIColor(white: 1.0, alpha: 0.9)
sectionHeaderView.addSubview(visualEffectView)
-1
let vc = UIViewController()
vc.view.frame = self.view!.frame
let efv = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.light))
efv.frame = vc.view.frame
vc.view.addSubview(efv)
self.addChildViewController(vc)
self.view.addSubview(vc.view)
// below method has a bug
// self.present(vc, animated: true, completion:nil)
-1

I would recommend adding your extraLightView to a view of UIColor(white: 1.0, alpha: 0.x), where x can be modified based on the scroll view's contentOffset. When there's nothing behind the view, your extraLightView will be white when x is 1. When you scroll and modify x, you won't be modifying the UIVisualEffectView (which is highly discouraged), but rather its parent view, which is perfectly safe.

slider
  • 2,736
  • 4
  • 33
  • 69
-1

Works with Swift 5

My way of making the visual effect view completely white when the background view is white.

let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blurView.backgroundColor = UIColor.white.withAlphaComponent(0.7)