Strange blur artefacts with UIVisualEffectView - ios

I have a UIVisualEffectView which is causing some strange artefacts right when it appears. The UIVisualEffectView is added in code after the view loads because it's not available in iOS 7.
Look at the blurred text background over the street view image: http://s.swic.name/Z3UL
The blur takes a good 0.1 seconds to appear, and before that it's just a lower resolution background shining through like the blur hasn't been calculated yet.
Any idea what is going on? I'm adding the blur in awakeFromNib using this code
- (void)addBlurWithColor:(UIColor *)color andStyle:(UIBlurEffectStyle)style andVibrancy:(BOOL)vibrancy
{
if (UIDevice.supportsVisualEffects && NSClassFromString(#"UIVisualEffectView") && !UIAccessibilityIsReduceTransparencyEnabled()) {
self.backgroundColor = color;
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:style];
UIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
visualEffectView.frame = self.bounds;
visualEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
NSArray *subviews = self.subviews;
[self addSubview:visualEffectView];
[self sendSubviewToBack:visualEffectView];
if (vibrancy) {
UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect];
UIVisualEffectView *vibrancyEffectView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect];
[vibrancyEffectView setFrame:self.bounds];
vibrancyEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
for (UIView *subview in subviews) {
[vibrancyEffectView.contentView addSubview:subview];
}
[[visualEffectView contentView] addSubview:vibrancyEffectView];
}
}
}
Edit: The video just has slow animations enabled, the transition is a regular push segue.
Edit2: I get the same strange behaviour if i just drag drop a "UIVisualEffectView with Blur" in Interface Builder, so the code above shouldn't be to blame.

Found the problem, when the view appears I'm reloading the tableview inside [UIView transitionWithView:...] which alters the alpha which apparently is a big no-no when it comes to UIVisualEffectViews!
According to UIVisualEffectView Documentation;
When using the UIVisualEffectView class, avoid alpha values that are
less than 1. Creating views that are partially transparent causes the
system to combine the view and all the associated subviews during an
offscreen render pass. UIVisualEffectView objects need to be combined
as part of the content they are layered on top of in order to look
correct. 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.
https://developer.apple.com/library/ios/documentation/uikit/reference/UIVisualEffectView/index.html

Related

How to use UIVisualEffectView with a custom subclass of UIView

I looked at this: How to use UIVisualEffectView? but I don't really understand how to apply it to my situation, after even looking at the comments.
I have a class SpecialHUD which is a subclass of HUDView which is a subclass of UIView. HUDView has its layer.contents set to be a background image (certain rectangle with rounded edges), and text in a label added as a subview. I can't change HUDView. I only need to change SpecialHUD.
I want the background image to act as the thing that blurs whatever is behind it - I can't get rid of the background image.
How do I use the UIVisualEffectView with my SpecialHUD?
I tried adding this to an update method in my SpecialHUD:
UIVisualEffect *blurEffect;
blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *visualEffectView;
visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
visualEffectView.frame = self.bounds;
[self addSubview:visualEffectView];
[[visualEffectView contentView] addSubview:self.label];
But it blurs everything in the rectangular container, and disregards the image — I'm not sure how to make everything work together.

Blur Effect in iOS8

I am trying to create blur effect on the UIView as well as I want to add vibrancy Effect so that I can highlight my buttons and use them. I am trying to learn via apple docs and couple of tutorials but unable to get in the following steps I am doing are correct.
Steps 1. I am creating a blur view adding it as subview to present view so that it looks blur.
Step 2 . Then i am creating a vibrancy view for the blur effect and adding it to contentview of blurview.
But i am unable to get the blur effect, can anyone help me to understand
1. If the following steps are correct.
2. If not how should ideally the sequence be.
3. How to highlight those buttons and use them. ( I am attaching the snaphot of my storyboard too).
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIBlurEffect *blurEffect= [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight];
UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
[blurView setUserInteractionEnabled:false];
[self.view addSubview:blurView];
UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect];
UIVisualEffectView *vibrancyView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect];
[vibrancyView setTranslatesAutoresizingMaskIntoConstraints:NO];
//[vibrancyView.contentView addSubview:s];
[blurView.contentView addSubview:vibrancyView];
}
The chief problem with your code is that you have neglected to give your blur view any size. Therefore it has zero size. Thus you will not see it do anything; it is, in effect, invisible. The most it can blur would be a single pixel behind it, and you are unlikely to notice that.
You are not setting the frame for your blurView and vibrancyView.
blurView.frame = self.view.frame;
vibrancyView.frame = self.view.frame;

iOS 8 UIVisualEffect View with UIBlurEffect becomes opaque

I am trying to present a view with a blurred transparent background over an existing view. I am able to get the desired effect during the presentation animation, but once the blurred view is fully presented, it becomes opaque.
Here's how I am presenting it:
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurEffectView.frame = self.view.bounds;
[blurEffectView setOpaque:NO];
[self.view insertSubview:blurEffectView atIndex:0];
[blurEffectView setTranslatesAutoresizingMaskIntoConstraints:false];
}
In my blurred view, I have set the background color to clear color and opaque to NO. Any thoughts?
Note that any UIVisualEffectView with a UIBlurEffect will be opaque if the "Reduce Transparency" accessibility setting is on.
(Settings > General > Accessibility > Increase Contrast > Reduce Transparency)
(Which is what your are actually checking with your if (!UIAccessibilityIsReduceTransparencyEnabled()) statement.)
I wound up writing a custom presentation controller. Never found a way to do this with standard presentations.
In the view where I want the effect viewDidLoad:
//Custom Modal Presentation
[self setModalPresentationStyle:UIModalPresentationCustom];
//blur the controlContainerView
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:[self blurStyle]];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurEffectView.frame = self.controlContainerView.bounds;
[blurEffectView setOpaque:NO];
[self.controlContainerView insertSubview:blurEffectView atIndex:0];
[blurEffectView setTranslatesAutoresizingMaskIntoConstraints:false];
}
Invoking the above view:
//in .h
#property (nonatomic) id<UIViewControllerTransitioningDelegate> transitioningDelegate;
//In .m
BlurViewController *blurVC = [self.storyboard instantiateViewControllerWithIdentifier:#"blurView"];
_transitioningDelegate = [[MyCustomOverlayDelegate alloc] init];
[blurView setModalPresentationStyle:UIModalPresentationCustom];
[blurView setTransitioningDelegate:[self transitioningDelegate]];
[blurView setBlurStyle:UIBlurEffectStyleLight];
[self presentViewController: blurView animated:YES completion:nil];
You will also need to import the entire stack of presentation controller classes. For me, these were OverlayDelegate, OverlayTransitionAnimator, and OverlayPresentationController. This is where the real work is.
If you can, check out WWDC 2014 session 228 "A look inside presentation controllers". Custom presentations are covered about half-way through. It was what I used to base my custom stack on and reasonably straight forward.
From the documentation:
A UIBlurEffect object applies a blurring effect to the content layered behind a UIVisualEffectView.
So I think the problem is your clear backgroundColor. If you have some kind of content or coloring behind your UIVisualEffectView, it should work.
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight];
UIVisualEffectView *blurredView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
This simple code snippet does work for me. In the ViewController I want to show the blurred effect I just call [[self view] addSubView:blurredView]
Simply set the view controller which holds the blurred view like this:
myViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
It prevents optimisation which removes views from the view hierarchy which in turn makes the blurred view presented first and thus making it opaque.

Performance issue with UIVisualEffectView in UITableViewCell

I have UITableView with 10 big images like Instagram and i have some blurview on that images. I add my visualeffects UITableViewCell like that;
_blurButtonEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
_visualButtonEffectView = [[UIVisualEffectView alloc] initWithEffect:_blurButtonEffect];
_visualButtonEffectView.layer.cornerRadius = 10.0f;
_visualEmojiEffectView = [[UIVisualEffectView alloc] initWithEffect:_blurButtonEffect];
_visualEmojiEffectView.layer.cornerRadius = 24.0f;
_visualButtonEffectView.layer.masksToBounds = YES;
_visualEmojiEffectView.layer.masksToBounds = YES;
_visualButtonEffectView.frame = CGRectMake(20.0f, _button.frame.origin.y + 7.0f, 48.0f, 48.0f);
_visualEmojiEffectView.frame = CGRectMake(_emoji.frame.origin.x - 4.0f, _button.frame.origin.y + 22.0f, ([UIScreen mainScreen].bounds.size.width/2) - 25.0f, 30.0f);
If button title is null, i show _visualEmojiEffectView and hide _visualButtonEffectView. Otherwise _visualButtonEffectView is always shown. While i'm scrolling my UITableView, performance is perfect but this blur effects shown so annoying, they seems like flashing with every scroll movement and touches. I can block this with this in cell;
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
If i'm rasterize my cell like that, blur effect works perfect but scrolling performance is awful even on iPhone 6. What is the correct way to configure UIBlurEffectView with UITableViewCell?
I know it is too late to reply but still i hope this helps someone. You can give a white background colour to the visual effect view with a very low alpha. Something like this:
[_visualButtonEffectView setBackgroundColor:[UIColor colorWithWhite:1.0 alpha:0.2]];
You may also try using UIToolbar instead of a visual effect view. It does not give exactly the same effect but will still be good enough.

iOS 7 Translucent Modal View Controller

The App Store app on iOS 7 uses a frosted glass-type effect where it is possible to see the view behind. Is this using an API built into iOS 7 or is it custom code. I was hoping it would be the former but I can't see any obvious references in the documentation. Obvious things like (like setting the alpha property on the modal view) don't seem to have any effect.
To see an example, open the App Store app and press the button at the top-right.
With the release of iOS 8.0, there is no need for getting an image and blurring it anymore. As Andrew Plummer pointed out, you can use UIVisualEffectView with UIBlurEffect.
UIViewController * contributeViewController = [[UIViewController alloc] init];
UIBlurEffect * blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *beView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
beView.frame = self.view.bounds;
contributeViewController.view.frame = self.view.bounds;
contributeViewController.view.backgroundColor = [UIColor clearColor];
[contributeViewController.view insertSubview:beView atIndex:0];
contributeViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:contributeViewController animated:YES completion:nil];
Solution that works before iOS 8
I would like to extend on rckoenes' answer:
As emphasised, you can create this effect by:
Convert the underlying UIView to an UIImage
Blur the UIImage
Set the UIImage as background of your view.
Sounds like a lot of work, but is actually done pretty straight-forward:
1. Create a category of UIView and add the following method:
-(UIImage *)convertViewToImage
{
UIGraphicsBeginImageContext(self.bounds.size);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
2. Make an image of the current view and blur it by using Apple's Image Effect category (download)
UIImage* imageOfUnderlyingView = [self.view convertViewToImage];
imageOfUnderlyingView = [imageOfUnderlyingView applyBlurWithRadius:20
tintColor:[UIColor colorWithWhite:1.0 alpha:0.2]
saturationDeltaFactor:1.3
maskImage:nil];
3. Set it as background of your overlay.
-(void)viewDidLoad
{
self.view.backgroundColor = [UIColor clearColor];
UIImageView* backView = [[UIImageView alloc] initWithFrame:self.view.frame];
backView.image = imageOfUnderlyingView;
backView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.view addSubview:backView];
}
Just reimplemented Sebastian Hojas' solution in Swift:
1. Create a UIView extension and add the following method:
extension UIView {
func convertViewToImage() -> UIImage{
UIGraphicsBeginImageContext(self.bounds.size);
self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return image;
}
}
2. Make an image of the current view and blur it by using Apple's Image Effect (I found a reimplementation of this in Swift here: SwiftUIImageEffects
var imageOfUnderlyingView = self.view.convertViewToImage()
imageOfUnderlyingView = imageOfUnderlyingView.applyBlurWithRadius(2, tintColor: UIColor(white: 0.0, alpha: 0.5), saturationDeltaFactor: 1.0, maskImage: nil)!
3. Set it as background of your overlay.
let backView = UIImageView(frame: self.view.frame)
backView.image = imageOfUnderlyingView
backView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
view.addSubview(backView)
I think this is the easiest solution for a modal view controller that overlays everything with a nice blur (iOS8)
UIViewController * contributeViewController = [[UIViewController alloc] init];
UIBlurEffect * blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *beView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
beView.frame = self.view.bounds;
contributeViewController.view.frame = self.view.bounds;
contributeViewController.view.backgroundColor = [UIColor clearColor];
[contributeViewController.view insertSubview:beView atIndex:0];
contributeViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:contributeViewController animated:YES completion:nil];
There is no API available in the iOS 7 SDK which will allow you to "frost" the underlaying view controller.
What I have done is render the underlaying view to an image, which I then frosted and set that as background the the view that is being presented.
Apple provides a good example for this: https://developer.apple.com/downloads/index.action?name=WWDC%202013
The project you want is called, iOS_RunningWithASnap
A little simplier way to achieve this (based on Andrew Plummer's answer) with Interface Builder (also it removes side effect that appears in Andrews answer):
In IB add Visual Effect View to your View Controller under your other views;
Make top, bottom, left, right constraints from Visual Effect View to top (parent) View, set all of them to 0;
Set Blur Style;
Add the code where you present your new fancy View Controller:
UIViewController *fancyViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"yourStoryboardIDFOrViewController"];
fancyViewController.view.backgroundColor = [UIColor clearColor];
fancyViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:fancyViewController
animated:YES
completion:nil];
Actually, the second and third lines are VERY important - otherwise controller will blink and then turn black.
Since iOS 8, this works:
let vc = UIViewController()
vc.view = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
vc.modalPresentationStyle = .OverFullScreen
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .OverFullScreen
presentViewController(nc, animated: true, completion: nil)
The key is the .OverFullScreen flag and ensuring the viewControllers have a blur UIVisualEffectView that is the first visible view.
As #rckoenes said, there is no Apple provided framework to get that effect. But some people out there already built good alternatives, like this one for example:
https://github.com/JagCesar/iOS-blur/
A couple of alternative approaches that also work on iOS 5 and 6:
FXBlurView: https://github.com/nicklockwood/FXBlurView
iOS RealtimeBlur: https://github.com/alexdrone/ios-realtimeblur
Fast & easy solution
with XIB support you can use for the old school boys
https://github.com/cezarywojcik/CWPopup
Instead of presenting the viewController as a modalView, you could add it as a child viewController and create a custom animation. You would then only need to change the default view of the viewController to a UIToolBar in viewDidLoad.
This will allow you to mimic the appstore's blurred modal view as closely as possible.
I have uploaded my take of the blurred view controller to [GitHub][1]. It also comes with a segue subclass so you can use it in your storyboards.
Repository: https://github.com/datinc/DATBlurSegue
Apple released the UIImageEffect category for those effects. Those category should be added manually to the project, and it support iOS7.
You can use UIToolbar as background.
By default UIToolbar have 50px height.
Add auto layout constraints on UIToolbar.
Then select height constraint and modify it.
Hierarchy will look like this:
UIView -> clear colour for background.
- UIToolbar
- Other contents.

Resources