I have an app I created that uses UIBlurEffectView
that worked perfect on iOS 9 and under, but when I upgraded my device (a few of them, not just 1 device) the blur disappeared and instead of the blur there is a half-transperant view for some reason.
Does anything changed in this class? Anyone knows why?
My code (The view is a shape from SVG file that I'm getting using PocketSVG API):
let blur: UIBlurEffect = UIBlurEffect(style: .Light)
let ev: UIVisualEffectView = UIVisualEffectView(effect: blur)
ev.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(ev)
ev.rightAnchor.constraintEqualToAnchor(self.rightAnchor).active = true
ev.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
ev.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active = true
ev.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 1.5).active = true
let myPath: CGPathRef = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()
var transform: CGAffineTransform = CGAffineTransformMakeScale(self.frame.size.width / 754.0, self.frame.size.height / 220.0)
let transformedPath: CGPathRef = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)!
let myShapeLayer = CAShapeLayer()
myShapeLayer.path = transformedPath
self.layer.mask = myShapeLayer
Leo Natan's answer code:
What you've suggested doesn't work, here is the code
override func layoutSubviews() {
let blur: UIBlurEffect = UIBlurEffect(style: .Light)
let ev: UIVisualEffectView = UIVisualEffectView(effect: blur)
ev.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(ev)
ev.rightAnchor.constraintEqualToAnchor(self.rightAnchor).active = true
ev.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
ev.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active = true
ev.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 1.5).active = true
let myPath: CGPathRef = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()
var transform: CGAffineTransform = CGAffineTransformMakeScale(self.frame.size.width / 754.0, self.frame.size.height / 220.0)
let transformedPath: CGPathRef = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)!
let myShapeLayer = CAShapeLayer()
myShapeLayer.path = transformedPath
self.layer.mask = myShapeLayer
let myMaskedView = UIView(frame: ev.frame)
myMaskedView.layer.mask = myShapeLayer
ev.maskView = myMaskedView
}
Konrad Siemczyk answer code
override func layoutSubviews() {
let blur: UIBlurEffect = UIBlurEffect(style: .Light)
let ev: UIVisualEffectView = UIVisualEffectView(effect: blur)
ev.frame = self.bounds
ev.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(ev)
ev.rightAnchor.constraintEqualToAnchor(self.rightAnchor).active = true
ev.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
ev.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active = true
ev.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 1.5).active = true
let myPath: CGPathRef = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()
var transform: CGAffineTransform = CGAffineTransformMakeScale(self.frame.size.width / 754.0, self.frame.size.height / 220.0)
let transformedPath: CGPathRef = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)!
let myShapeLayer = CAShapeLayer()
myShapeLayer.path = transformedPath
//self.layer.mask = myShapeLayer
myShapeLayer.fillRule = kCAFillRuleEvenOdd
let myMaskedView = UIView(frame: self.frame)
myMaskedView.backgroundColor = UIColor.blackColor()
myMaskedView.layer.mask = myShapeLayer
ev.maskView = myMaskedView
}
Thanks to @emrahgunduz answer I managed to update the above code to Swift 5.2.
import UIKit
import PlaygroundSupport
let generalFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let containerView = UIView(frame: generalFrame)
containerView.backgroundColor = UIColor.black;
PlaygroundPage.current.liveView = containerView
let parentView = UIView(frame: generalFrame)
containerView.addSubview(parentView)
let url = URL(string: "https://static.pexels.com/photos/168066/pexels-photo-168066-large.jpeg")
let data = try Data(contentsOf: url!);
let imageView = UIImageView(frame:parentView.bounds)
imageView.image = UIImage(data: data)
imageView.contentMode = .scaleAspectFill
var roundedRect = CGRect (
x: 0.0,
y: 0.0,
width: parentView.bounds.size.width * 0.5,
height: parentView.bounds.size.width * 0.5
);
roundedRect.origin.x = parentView.frame.size.width / 2 - roundedRect.size.width / 2;
roundedRect.origin.y = parentView.frame.size.height / 2 - roundedRect.size.height / 2;
let cornerRadius = roundedRect.size.height / 2.0;
let path = UIBezierPath(rect:parentView.bounds)
let croppedPath = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius)
path.append(croppedPath)
path.usesEvenOddFillRule = true
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath;
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
let blurView = UIBlurEffect(style: .light)
let effectView = UIVisualEffectView(effect: blurView)
effectView.frame = generalFrame
effectView.layer.mask = maskLayer
parentView.addSubview(imageView)
parentView.addSubview(effectView)
TLDR: Please check this solution first:
https://stackoverflow.com/a/67939549/2829540
... even though these examples work on older versions of iOS, looks like newer ones require a layer instead of a view, this answer might not work as expected. You might need to implement this solution for older versions and the linked one for newer ones.
For ObjectiveC users out there.
Here is a working example for iOS 10. I also attached the resulting view at the end. I am adding the white border on top later. The cropped circle masking is in the code, if you like it use it as is.
// "self" in here is an UIView that contains some images inside.
{
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurredEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
CGRect frame = self.frame;
frame.origin = CGPointMake (0, 0);
blurredEffectView.frame = frame;
[self addSubview:blurredEffectView];
UIView *maskView = [[UIView alloc] initWithFrame:frame];
maskView.backgroundColor = [UIColor blackColor];
__weak UIView *weak = self;
maskView.layer.mask = ({ // This mask draws a rectangle and crops a circle inside it.
__strong UIView *strong = weak;
CGRect roundedRect = CGRectMake (
0,
0,
strong.frame.size.width * 0.8f,
strong.frame.size.width * 0.8f
);
roundedRect.origin.x = strong.frame.size.width / 2 - roundedRect.size.width / 2;
roundedRect.origin.y = strong.frame.size.height / 2 - roundedRect.size.height / 2;
CGFloat cornerRadius = roundedRect.size.height / 2.0f;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
UIBezierPath *croppedPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:cornerRadius];
[path appendPath:croppedPath];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = path.CGPath;
mask.fillRule = kCAFillRuleEvenOdd;
mask;
});
blurredEffectView.maskView = maskView;
}
So, this is same code as Swift 3 for testing in playground.
This is using a try while downloading the url, so it is synchronous
import UIKit
import PlaygroundSupport
let generalFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let containerView = UIView(frame: generalFrame)
containerView.backgroundColor = UIColor.black;
PlaygroundPage.current.liveView = containerView
let parentView = UIView(frame: generalFrame)
containerView.addSubview(parentView)
let url = URL(string: "https://static.pexels.com/photos/168066/pexels-photo-168066-large.jpeg")
let data = try Data(contentsOf: url!);
let imageView = UIImageView(frame:parentView.bounds)
imageView.image = UIImage(data: data)
imageView.contentMode = .scaleAspectFill
let maskView = UIView(frame:parentView.bounds)
maskView.backgroundColor = UIColor.black
maskView.layer.mask = {() -> CALayer in
var roundedRect = CGRect (
x: 0.0,
y: 0.0,
width: parentView.bounds.size.width * 0.5,
height: parentView.bounds.size.width * 0.5
);
roundedRect.origin.x = parentView.frame.size.width / 2 - roundedRect.size.width / 2;
roundedRect.origin.y = parentView.frame.size.height / 2 - roundedRect.size.height / 2;
let cornerRadius = roundedRect.size.height / 2.0;
let path = UIBezierPath(rect:parentView.bounds)
let croppedPath = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius)
path.append(croppedPath)
path.usesEvenOddFillRule = true
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath;
maskLayer.fillRule = kCAFillRuleEvenOdd
return maskLayer
}()
let blurView = UIBlurEffect(style: .light)
let effectView = UIVisualEffectView(effect: blurView)
effectView.frame = generalFrame
effectView.mask = maskView
parentView.addSubview(imageView)
parentView.addSubview(effectView)
And working example in a view controller:
This one downloads an image first then appends the blur effect.
import UIKit
class ViewController: UIViewController {
func addTheBlurView(data :Data) {
let generalFrame = self.view.bounds;
let parentView = UIView(frame: generalFrame)
self.view.addSubview(parentView)
let imageView = UIImageView(frame: parentView.bounds)
imageView.image = UIImage(data: data)
imageView.contentMode = .scaleAspectFill
let maskView = UIView(frame: parentView.bounds)
maskView.backgroundColor = UIColor.black
maskView.layer.mask = {
() -> CALayer in
var roundedRect = CGRect(
x: 0.0,
y: 0.0,
width: parentView.bounds.size.width * 0.5,
height: parentView.bounds.size.width * 0.5
);
roundedRect.origin.x = parentView.frame.size.width / 2 - roundedRect.size.width / 2;
roundedRect.origin.y = parentView.frame.size.height / 2 - roundedRect.size.height / 2;
let cornerRadius = roundedRect.size.height / 2.0;
let path = UIBezierPath(rect: parentView.bounds)
let croppedPath = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius)
path.append(croppedPath)
path.usesEvenOddFillRule = true
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath;
maskLayer.fillRule = kCAFillRuleEvenOdd
return maskLayer
}()
let blurView = UIBlurEffect(style: .light)
let effectView = UIVisualEffectView(effect: blurView)
effectView.frame = generalFrame
effectView.mask = maskView
parentView.addSubview(imageView)
parentView.addSubview(effectView)
}
override func viewDidLoad() {
debugPrint("Running...")
super.viewDidLayoutSubviews();
// Lets load an image first, so blur looks cool
let url = URL(string: "https://static.pexels.com/photos/168066/pexels-photo-168066-large.jpeg")
URLSession.shared.dataTask(with: url!) {
(data, response, error) in
if error != nil {
print(error)
return
}
DispatchQueue.main.async(execute: {
self.addTheBlurView(data: data!)
})
}.resume()
}
}
OBJECTIVEC VERSION
PLAYGROUND VERSION
VIEWCONTROLLER VERSION
According to a discussion with Apple engineer, this is a limitation to how the UIVisualEffectView
works. It used to work before, but UIVisualEffectView
was less accurate.
The suggested approach in the discussion is to use maskView
instead of masking the layer directly. So try creating a view, mask that view's layer, and set that as the mask view.
let myMaskedView = UIView(frame: ev.frame)
myMaskedView.layer.mask = myShapeLayer
ev.maskView = myMaskedView
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With