In the Music app of the new iOS, we can see an album cover behind a view that blurs it.

How can something like that be accomplished? I've read the documentation, but did not find anything there.


Solution 1:

You can use UIVisualEffectView to achieve this effect. This is a native API that has been fine-tuned for performance and great battery life, plus it's easy to implement.

Swift:

//only apply the blur if the user hasn't disabled transparency effects
if !UIAccessibility.isReduceTransparencyEnabled {
    view.backgroundColor = .clear

    let blurEffect = UIBlurEffect(style: .dark)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    //always fill the view
    blurEffectView.frame = self.view.bounds
    blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    view.addSubview(blurEffectView) //if you have more UIViews, use an insertSubview API to place it where needed
} else {
    view.backgroundColor = .black
}

Objective-C:

//only apply the blur if the user hasn't disabled transparency effects
if (!UIAccessibilityIsReduceTransparencyEnabled()) {
    self.view.backgroundColor = [UIColor clearColor];

    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    //always fill the view
    blurEffectView.frame = self.view.bounds;
    blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self.view addSubview:blurEffectView]; //if you have more UIViews, use an insertSubview API to place it where needed
} else {
    self.view.backgroundColor = [UIColor blackColor];
}

If you are presenting this view controller modally to blur the underlying content, you'll need to set the modal presentation style to Over Current Context and set the background color to clear to ensure the underlying view controller will remain visible once this is presented overtop.

Solution 2:

Core Image

Since that image in the screenshot is static, you could use CIGaussianBlur from Core Image (requires iOS 6). Here is sample: https://github.com/evanwdavis/Fun-with-Masks/blob/master/Fun%20with%20Masks/EWDBlurExampleVC.m

Mind you, this is slower than the other options on this page.

#import <QuartzCore/QuartzCore.h>

- (UIImage*) blur:(UIImage*)theImage
{   
    // ***********If you need re-orienting (e.g. trying to blur a photo taken from the device camera front facing camera in portrait mode)
    // theImage = [self reOrientIfNeeded:theImage];

    // create our blurred image
    CIContext *context = [CIContext contextWithOptions:nil];
    CIImage *inputImage = [CIImage imageWithCGImage:theImage.CGImage];

    // setting up Gaussian Blur (we could use one of many filters offered by Core Image)
    CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
    [filter setValue:inputImage forKey:kCIInputImageKey];
    [filter setValue:[NSNumber numberWithFloat:15.0f] forKey:@"inputRadius"];
    CIImage *result = [filter valueForKey:kCIOutputImageKey];

    // CIGaussianBlur has a tendency to shrink the image a little, 
    // this ensures it matches up exactly to the bounds of our original image
    CGImageRef cgImage = [context createCGImage:result fromRect:[inputImage extent]];

    UIImage *returnImage = [UIImage imageWithCGImage:cgImage];//create a UIImage for this function to "return" so that ARC can manage the memory of the blur... ARC can't manage CGImageRefs so we need to release it before this function "returns" and ends.
    CGImageRelease(cgImage);//release CGImageRef because ARC doesn't manage this on its own.

    return returnImage;

    // *************** if you need scaling
    // return [[self class] scaleIfNeeded:cgImage];
}

+(UIImage*) scaleIfNeeded:(CGImageRef)cgimg {
    bool isRetina = [[[UIDevice currentDevice] systemVersion] intValue] >= 4 && [[UIScreen mainScreen] scale] == 2.0;
    if (isRetina) {
        return [UIImage imageWithCGImage:cgimg scale:2.0 orientation:UIImageOrientationUp];
    } else {
        return [UIImage imageWithCGImage:cgimg];
    }
}

- (UIImage*) reOrientIfNeeded:(UIImage*)theImage{

    if (theImage.imageOrientation != UIImageOrientationUp) {

        CGAffineTransform reOrient = CGAffineTransformIdentity;
        switch (theImage.imageOrientation) {
            case UIImageOrientationDown:
            case UIImageOrientationDownMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.width, theImage.size.height);
                reOrient = CGAffineTransformRotate(reOrient, M_PI);
                break;
            case UIImageOrientationLeft:
            case UIImageOrientationLeftMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.width, 0);
                reOrient = CGAffineTransformRotate(reOrient, M_PI_2);
                break;
            case UIImageOrientationRight:
            case UIImageOrientationRightMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, 0, theImage.size.height);
                reOrient = CGAffineTransformRotate(reOrient, -M_PI_2);
                break;
            case UIImageOrientationUp:
            case UIImageOrientationUpMirrored:
                break;
        }

        switch (theImage.imageOrientation) {
            case UIImageOrientationUpMirrored:
            case UIImageOrientationDownMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.width, 0);
                reOrient = CGAffineTransformScale(reOrient, -1, 1);
                break;
            case UIImageOrientationLeftMirrored:
            case UIImageOrientationRightMirrored:
                reOrient = CGAffineTransformTranslate(reOrient, theImage.size.height, 0);
                reOrient = CGAffineTransformScale(reOrient, -1, 1);
                break;
            case UIImageOrientationUp:
            case UIImageOrientationDown:
            case UIImageOrientationLeft:
            case UIImageOrientationRight:
                break;
        }

        CGContextRef myContext = CGBitmapContextCreate(NULL, theImage.size.width, theImage.size.height, CGImageGetBitsPerComponent(theImage.CGImage), 0, CGImageGetColorSpace(theImage.CGImage), CGImageGetBitmapInfo(theImage.CGImage));

        CGContextConcatCTM(myContext, reOrient);

        switch (theImage.imageOrientation) {
            case UIImageOrientationLeft:
            case UIImageOrientationLeftMirrored:
            case UIImageOrientationRight:
            case UIImageOrientationRightMirrored:
                CGContextDrawImage(myContext, CGRectMake(0,0,theImage.size.height,theImage.size.width), theImage.CGImage);
                break;

            default:
                CGContextDrawImage(myContext, CGRectMake(0,0,theImage.size.width,theImage.size.height), theImage.CGImage);
                break;
        }

        CGImageRef CGImg = CGBitmapContextCreateImage(myContext);
        theImage = [UIImage imageWithCGImage:CGImg];

        CGImageRelease(CGImg);
        CGContextRelease(myContext);
    }

    return theImage;
}

Stack blur (Box + Gaussian)

  • StackBlur This implements a mix of Box and Gaussian blur. 7x faster than non accelerated gaussian, but not so ugly as box blur. See a demo in here (Java plugin version) or here (JavaScript version). This algorithm is used in KDE and Camera+ and others. It doesn't use the Accelerate Framework but it's fast.

Accelerate Framework

  • In the session “Implementing Engaging UI on iOS” from WWDC 2013 Apple explains how to create a blurred background (at 14:30), and mentions a method applyLightEffect implemented in the sample code using Accelerate.framework.

  • GPUImage uses OpenGL shaders to create dynamic blurs. It has several types of blur: GPUImageBoxBlurFilter, GPUImageFastBlurFilter, GaussianSelectiveBlur, GPUImageGaussianBlurFilter. There is even a GPUImageiOSBlurFilter that “should fully replicate the blur effect provided by iOS 7's control panel” (tweet, article). The article is detailed and informative.

    -(UIImage *)blurryGPUImage:(UIImage *)image withBlurLevel:(NSInteger)blur {
        GPUImageFastBlurFilter *blurFilter = [GPUImageFastBlurFilter new];
        blurFilter.blurSize = blur;
        UIImage *result = [blurFilter imageByFilteringImage:image];
        return result;
    }
  • From indieambitions.com: Perform a blur using vImage. The algorithm is also used in iOS-RealTimeBlur.

  • From Nick Lockwood: https://github.com/nicklockwood/FXBlurView The example shows the blur over a scroll view. It blurs with dispatch_async, then syncs to call updates with UITrackingRunLoopMode so the blur is not lagged when UIKit gives more priority to the scroll of the UIScrollView. This is explained in Nick's book iOS Core Animation, which btw it's great.

  • iOS-blur This takes the blurring layer of the UIToolbar and puts it elsewhere. Apple will reject your app if you use this method. See https://github.com/mochidev/MDBlurView/issues/4

  • From Evadne blog: LiveFrost: Fast, Synchronous UIView Snapshot Convolving. Great code and a great read. Some ideas from this post:

    • Use a serial queue to throttle updates from CADisplayLink.
    • Reuse bitmap contexts unless bounds change.
    • Draw smaller images using -[CALayer renderInContext:] with a 0.5f scale factor.

Other stuff

Andy Matuschak said on Twitter: “you know, a lot of the places where it looks like we're doing it in real time, it's static with clever tricks.”

At doubleencore.com they say “we’ve found that a 10 pt blur radius plus a 10 pt increase in saturation best mimics iOS 7’s blur effect under most circumstances”.

A peek at the private headers of Apple's SBFProceduralWallpaperView.

Finally, this isn't a real blur, but remember you can set rasterizationScale to get a pixelated image: http://www.dimzzy.com/blog/2010/11/blur-effect-for-uiview/

Solution 3:

I decided to post a written Objective-C version from the accepted answer just to provide more options in this question..

- (UIView *)applyBlurToView:(UIView *)view withEffectStyle:(UIBlurEffectStyle)style andConstraints:(BOOL)addConstraints
{
  //only apply the blur if the user hasn't disabled transparency effects
  if(!UIAccessibilityIsReduceTransparencyEnabled())
  {
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:style];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    blurEffectView.frame = view.bounds;

    [view addSubview:blurEffectView];

    if(addConstraints)
    {
      //add auto layout constraints so that the blur fills the screen upon rotating device
      [blurEffectView setTranslatesAutoresizingMaskIntoConstraints:NO];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeTop
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeTop
                                                      multiplier:1
                                                        constant:0]];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeBottom
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeBottom
                                                      multiplier:1
                                                        constant:0]];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeLeading
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeLeading
                                                      multiplier:1
                                                        constant:0]];

      [view addConstraint:[NSLayoutConstraint constraintWithItem:blurEffectView
                                                       attribute:NSLayoutAttributeTrailing
                                                       relatedBy:NSLayoutRelationEqual
                                                          toItem:view
                                                       attribute:NSLayoutAttributeTrailing
                                                      multiplier:1
                                                        constant:0]];
    }
  }
  else
  {
    view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.7];
  }

  return view;
}

The constraints could be removed if you want incase if you only support portrait mode or I just add a flag to this function to use them or not..

Solution 4:

I don't think I'm allowed to post the code, but the above post mentioning the WWDC sample code is correct. Here is the link: https://developer.apple.com/downloads/index.action?name=WWDC%202013

The file you're looking for is the category on UIImage, and the method is applyLightEffect.

As I noted above in a comment, the Apple Blur has saturation and other things going on besides blur. A simple blur will not do... if you are looking to emulate their style.

Solution 5:

Here's an easy way to add custom blur without haggling with private APIs using UIViewPropertyAnimator:

First, declare class property:

var blurAnimator: UIViewPropertyAnimator!

Then set your blur view in viewDidLoad():

let blurEffectView = UIVisualEffectView()
blurEffectView.backgroundColor = .clear
blurEffectView.frame = view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(blurEffectView)

blurAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [blurEffectView] in
    blurEffectView.effect = UIBlurEffect(style: .light)
}

blurAnimator.fractionComplete = 0.15 // set the blur intensity.    

Note: This solution is not suitable for UICollectionView/UITableView cells