Fix UIVisualEffectView extra light blur being gray on white background - ios

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:
and kind of work on colors, too:
but when it comes to a white background this happens:
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?

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.

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.

You can try :
var visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
visualEffectView.frame = imageView.bounds
imageView.addSubview(visualEffectView)

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)

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.

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.

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)

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.

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)

Related

Just want to add a darken layer or make the UIImageView darken by Swift

As people already know that this seems simple but Stackoverflow has no answers for me. Basically I want to make an image darker like the bg image of Beijing ancient building below:
to make it darker so that the tags on it can be more contrasting obvious.
I tried adding a layer which is one of the answers from Stackoverflow or adding tintColor but none of them worked, is there any method that can really work? Thank you, guys.
I'm under iOS 13 and swift 5.1
Add this UIView extension to your project
extension UIView {
func addoverlay(color: UIColor = .black,alpha : CGFloat = 0.6) {
let overlay = UIView()
overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
overlay.frame = bounds
overlay.backgroundColor = color
overlay.alpha = alpha
addSubview(overlay)
}
//This function will add a layer on any `UIView` to make that `UIView` look darkened
}
then use it like on any UIView(In your Case yourImageView)
yourImageView.addoverlay()
Or you can specify your own overlay color and alpha value
yourImageView.addoverlay(color: .blue, alpha: 0.5)

How do I get the grey color/translucency I see on UIPickerDate default view on my own custom Picker?

I have created a UI Picker but the default is just it being transparent. I would like to use/be given the RGB + translucency values that makes the grey color that is commonly used on picker views.
It is the same grey like the defualt color for a programmatic UI Date Picker. Thank you! I need this to be in alignment with Apple's Human Interface Guidelines.
Added a picture of the background color/translucency I need below:
iPhone SDK has two types of gray color both have opacity 1.
iOS 13 also introduces a range of six opaque gray colors you can use in rare cases where translucency doesn't work well.
https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color/
And for changing the background color.
light gray color:
picker.backgroundColor = UIColor.lightGray
dark gray color:
picker.backgroundColor = UIColor.darkGray
if you want some transparency on the picker view. You can add Gradient on it.
let view = UIView(frame: CGRect(x: 0, y: 0, width: picker.frame.width, height: picker.frame.height))
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [UIColor.white.cgColor, UIColor.lightGray.cgColor]
picker.layer.insertSublayer(gradient, at: 0)
find the image as it looks with gradient and light gray color.
picker view with gradient
Hex color - #e8e9ed
RGB color - UIColor(red: 232/255, green: 233/255, blue: 237/255, alpha: 1)
or you can use #f7f7f7
I hope it works.

What do I need for masking a UIImageView and how do I do it in Swift 3?

I was wondering what I would need if I wanted to use a mask image to get my UIImageView in a specific shape. From what I understand, to create a mask, I need to have an image with the shape of the mask all black on top of a white background. Something like this, for example:
First of all, is this sufficient to shape an image view, and if so, how do I do it in Swift 3? I can only find masking code that is either outdated or written in Objective-C. I've tried simply assigning the image above to an UIImageView and then assign the image view to the mask property of the UIImageView I want to shape, like so:
self.defaultImageView.mask = self.maskImageView
This didn't do anything. It just made self.maskImageView disappear (both image view's added through the storyboard and connected using IBOutlet properties). I'm sure I'm forgetting to do something. It can't be this simple. I would appreciate it if someone could help me out. Like I said, I put both image views on the exact same spot, on top of each other, in the storyboard.
UPDATE:
My first attempt to set the mask programmatically after deleting it from my storyboard.
let layer:CALayer = CALayer()
let mask:UIImage = UIImage(named: "Black-Star-Photographic-Agency")!
layer.contents = mask
layer.frame = CGRect(x: 0, y: 0, width: ((self.defaultImageView.image?.size.width)!), height: (self.defaultImageView.image?.size.height)!)
self.defaultImageView.layer.mask = layer
self.defaultImageView.layer.masksToBounds = true
The result was that the image view had completely disappeared and wasn't visible anymore. Am I doing something, am I forgetting something or both?
You should use a png image, which supports transparency, unlike jpg.
In Photoshop your image should look similar to this:
It doesn't matter if your shape is black or white. What matters is transparency of each pixel. Opaque area (black in this case) will be visible and transparent area will get trimmed.
Edit:
You should not create mask view from storyboard if you do so. It is not going to be a part of your view hierarchy. Just add it programmatically like this:
let maskView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
maskView.image = UIImage(named: "mask")
imageView.mask = maskView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
maskView.frame = imageView.bounds
}
Output:
Here is a test project to show how it's working.
Also if you're using a custom frame/image and run into the mask not showing properly, try setting the content mode of the mask:
maskView.contentMode = .scaleAspectFit

Create shadow around a UIVisualEffectView without covering the whole view

Is it possible to create a shadow around a UIVisualView with UIBlurEffect without letting the UIVisualView get coloured by the shadow underneath?
I basically just want the shadow around the view but with this code the shadow will cover the whole view which darkens the whole view to much:
let borderPath = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 15, height: 15)).cgPath
shadowView.frame = view.bounds
shadowView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowRadius = 3.0
shadowView.backgroundColor = UIColor.clear
shadowView.layer.shadowPath = borderPath
shadowView.layer.shadowOffset = CGSize(width: 0, height: 0)
self.view.insertSubview(shadowView, at: 0)
let blurEffect = UIBlurEffect(style: .extraLight)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = view.bounds
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blurView.clipsToBounds = true
blurView.layer.cornerRadius = 15
view.insertSubview(blurView, aboveSubview: shadowView)
EDIT.
I need to achieve the same thing as in Apple's Maps application. Where the draggable favourite view both uses the UIVisualEffectView and a shadow around its top, without interfering with the UIVisualEffectView's background.
See example screenshots:
Ok, so the problem was that my background in the underlying view was white. And with the UIBlurEffect .extraLight used on a background which is lighter than the BlurEffect the shadow beneath a UIVisualView appears darker than with a more vivid background.
Also described in this question:
Fix UIVisualEffectView extra light blur being gray on white background
UPDATE
I found a project explaining how to solve this on Github The solution involves creating a 9-part UIImage to represent the shadow. The creator also explains the underlying layers of the iOS 10 Maps.
So i'm trying to recreate the look of iOS 10 maps. I decided to attach the maps app in the simulator to the debugger to see what was going on...
Apple actually get around this by having a UIImage over the top of the content with the border and shadow. Not the most elegant way to do it but i'm going for the exact look so I'm going to take the exact approach.
I also grabbed the asset (using this) from the Maps app to save making your own one. Shame they only have #2x artwork in it though :/
I found in iOS 12, this is not a problem, the UIVisualEffectView ignore the shadow of underneath views, it just sees through the shadow, like the shadow not exist.
The trick is to not set the shadowPath of the layer. If it is set, the shadow is painted below the visual effect view and, in consequence, darkens the blurred view.
The documentation for shadowPath states:
If you specify a value for this property, the layer creates its shadow
using the specified path instead of the layer’s composited alpha
channel.
The "instead…" part is what we actually need: the shadow should be composed after rendering the content, based on what the content leaves transparent. Now you might be deterred from not setting a shadowPath because the documentation also states that an "explicit path usually improves rendering performance". However, I didn't see any issues in real life so far. In comparison to the rendering cost of the UIVisualEffectView, painting the shadow doesn't make a difference, I assume.
Also make sure to set the shadow on a superview of the UIVisualEffectView, not on a sibling view.
I usually solve this kind of situation using composition of views. Instead of setting the shadow in the target view, I create a ShadowView and I put it behind the target view, both views with the same frame.
I've posted and example and code in this question.
An example of the result of this approach is the following:

Adding a layer of color over a UIImage

I was hoping to make my UIImage "highlight" briefly upon being tapped. Not sure of the color yet, but let's say blue for arguments sake. So you tap the image, it briefly looks blue and then it navigates you to a details page to edit something on another screen.
From some initial reading it seems the right course of action is to use the Quartz framework and do this:
imageView.layer.backgroundColor = UIColor.blueColor()
imageView.layer.opacity = 0.7
I guess the idea would be you change the background of the layer behind the image, and then by setting the opacity of the image, the blue "bleeds through" a little bit, giving you a slightly blue image?
When I try the above, however, a blue border goes around the image itself, and based upon the opacity, the blue is either dark or light. The actual image does not become any more blue, but it does react to the opacity (meaning if I set it to something like .1, the image is very faded and barely visible). Why does the image react correctly, but not show blue?
Thanks so much!
As far as I know changing the opacity will change the opacity for the WHOLE view, meaning not just the UIImage that the UIImageView holds. So instead of fading to reveal the UIImageView's background color, instead the opacity of the whole view is just decreased as you're seeing.
Another way you could do it though would be to add an initially transparent UIView on top of your UIImageView and change its opacity instead:
UIView *blueCover = [[UIView alloc] initWithFrame: myImageView.frame];
blueCover.backgroundColor = [UIColor blueColor];
blueCover.layer.opacity = 0.0f;
[self.view addSubview: blueCover];
[UIView animateWithDuration:0.2f animations^{
blueCover.layer.opacity = 0.5f
}];
Here's how I use tint and tint opacities in IOS 9 with Swift -
//apply a color to an image
//ref - http://stackoverflow.com/questions/28427935/how-can-i-change-image-tintcolor
//ref - https://www.captechconsulting.com/blogs/ios-7-tutorial-series-tint-color-and-easy-app-theming
func getTintedImage() -> UIImageView {
var image : UIImage
var imageView : UIImageView
image = UIImage(named: "someAsset")!
let size : CGSize = image.size
let frame : CGRect = CGRectMake((UIScreen.mainScreen().bounds.width-86)/2, 600, size.width, size.height)
let redCover : UIView = UIView(frame: frame)
redCover.backgroundColor = UIColor.redColor()
redCover.layer.opacity = 0.75
imageView = UIImageView()
imageView.image = image.imageWithRenderingMode(UIImageRenderingMode.Automatic)
imageView.addSubview(redCover)
return imageView
}
Is this tap highlight perhaps something you could do with a UIButton? UIButton has all these states off the bat and might be a bit easier to work with, specially if there's something that actually needs to happen after you tap it. Worst case scenario is you have a UIButton that does not trigger any method when tapped.
You can try changing the tint color instead:
UIImage *image = [UIImage imageNamed:#"yourImageAsset"];
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
imageView.tintColor = [UIColor blueColor];
imageView.image = image;

Resources