创建一个模糊的覆盖层视图

399
在新的iOS音乐应用中,我们可以看到一个模糊了的视图后面是一个专辑封面。如何实现这样的效果呢?我已经阅读了文档,但没有找到相关内容。
25个回答

595
你可以使用UIVisualEffectView来实现这个效果。这是一个本地API,已经过优化以提高性能和电池寿命,而且易于实现。 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];
}

如果您要以模态方式呈现此视图控制器以模糊底层内容,则需要将模态呈现样式设置为“ Over Current Context”,并将背景颜色设置为透明,以确保在此视图控制器呈现在顶部时底层视图控制器仍然可见。


9
为了澄清这段代码中的 insertSubView:belowSubView: 注释,我使用以下代码将模糊效果设置为视图的背景:view.insertSubview(blurEffectView, atIndex: 0) - Michael Voccola
3
参考上面的回答,有必要检查 "if (!UIAccessibilityIsReduceTransparencyEnabled())" 吗?或者我们可以跳过这个步骤吗? - GKK
3
如果您正在展示您的视图控制器,请确保将modalPresentationStyle设置为.overCurrentContext,并将背景颜色设置为clear。 - Shardul
3
非常好用!只需要做一个小改动:[self.view insertSubview:blurEffectView atIndex:1]; - Abhishek Thapliyal
3
在iOS 11中,我发现不必手动检查UIAccessibilityIsReduceTransparencyEnabled() - Nate Whittaker
显示剩余8条评论

289

核心图像

由于截图中的图像是静态的,因此您可以使用Core Image中的CIGaussianBlur(需要iOS 6)。这是一个示例:https://github.com/evanwdavis/Fun-with-Masks/blob/master/Fun%20with%20Masks/EWDBlurExampleVC.m

请注意,与此页面上的其他选项相比,这种方法速度较慢。

#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;
}

堆栈模糊 (Box + Gaussian)

  • StackBlur 实现了盒式模糊和高斯模糊的混合。比非加速的高斯模糊快7倍,但不像盒式模糊那么丑陋。在 这里(Java插件版本)或 这里(JavaScript版本)可以看到演示。此算法用于KDE、Camera+等软件中。它不使用Accelerate Framework,但速度很快。

Accelerate Framework

  • WWDC 2013的“实现iOS上引人入胜的UI”会议中,苹果解释了如何创建模糊的背景(在14:30处),并提到使用Accelerate.framework实现的一个applyLightEffect方法。

  • GPUImage使用OpenGL着色器创建动态模糊。它有几种类型的模糊:GPUImageBoxBlurFilter、GPUImageFastBlurFilter、GaussianSelectiveBlur、GPUImageGaussianBlurFilter。甚至还有一个GPUImageiOSBlurFilter,它“应该完全复制iOS 7控制面板提供的模糊效果”(tweetarticle)。这篇文章详细而且信息量大。

-(UIImage *)blurryGPUImage:(UIImage *)image withBlurLevel:(NSInteger)blur {
    GPUImageFastBlurFilter *blurFilter = [GPUImageFastBlurFilter new];
    blurFilter.blurSize = blur;
    UIImage *result = [blurFilter imageByFilteringImage:image];
    return result;
}
这段代码是用于使用GPUImage对图片进行模糊处理的。其中,blur参数表示模糊程度,值越大则图片越模糊。函数返回处理后的图片。

其他内容

Andy Matuschak 在 Twitter 上:“你知道,很多看起来像我们实时处理的地方,其实都是静态的,但有一些巧妙的技巧。”

doubleencore.com 上他们说:“我们发现,在大多数情况下,模糊半径为 10pt,饱和度增加 10pt 最能模仿 iOS 7 的模糊效果。”

查看 Apple 的 SBFProceduralWallpaperView 的私有头文件。

最后,这并不是真正的模糊效果,但请记住,您可以设置 rasterizationScale 来获得像素化的图像:http://www.dimzzy.com/blog/2010/11/blur-effect-for-uiview/


谢谢您的回答!一个问题已经解决了。但是我们还有一个问题。如何在iOS 7中获取封面图片?如果可能的话? - kondratyevdev
如果你是指如何从手机中获取背景壁纸图像,目前我不知道。在API差异中没有看到这个功能。也许它使用了私有API。 - Jano
有一件事我注意到的(可能我完全错了),就是苹果的模糊效果似乎还增加了一点色彩饱和度。所以,我觉得这不仅仅是简单的高斯模糊。 - xtravar
您是否知道是否可以将此效果应用于UITableViewCell,而不会影响性能? - Leonardo
只是为了明确,因为我一开始很困惑,applyLightEffect不是来自Accelerated框架。它是在WWDC 2013会议的示例代码中找到的一个方法。您可以在此处找到代码(iOS_UIImageEffects.zip):https://developer.apple.com/downloads/index.action?name=WWDC%202013# - Perishable Dave
显示剩余3条评论

18
这是一种使用UIViewPropertyAnimator添加自定义模糊而无需使用私有API的简单方法:
首先,声明类属性:
var blurAnimator: UIViewPropertyAnimator!

然后在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.    

注意:该解决方案不适用于UICollectionView/UITableView单元格。


2
这是我找到的唯一解决方案,如果你想控制UIVisualEffectView的透明度。 - Denis Kutlubaev

15

在这里输入图片描述

您可以轻松地从 Xcode 中进行操作。按照以下步骤:将视觉效果视图拖动到您的 uiview 或 imageview 上。

祝编码愉快 :)


15

我决定发布一份书面的Objective-C版本,以提供更多选项。

- (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;
}

如果您只支持竖屏模式,或者我在此函数中添加一个标志来使用它们或不使用它们,则可以删除限制。


1
对于新手(像我),调用上述方法的一种方式是:[self applyBlurToView:self.view withEffectStyle:UIBlurEffectStyleDark andConstraints:YES];(感谢NorthBlast) - tmr

14

我不认为我被允许发布代码,但上面提到WWDC示例代码的帖子是正确的。以下是链接:https://developer.apple.com/downloads/index.action?name=WWDC%202013

你需要找到的文件是UIImage中的分类,并且方法是applyLightEffect。

正如我在评论中提到的,苹果模糊效果除了模糊之外还有饱和度和其他一些因素。如果你想要模仿他们的风格,仅仅进行简单的模糊处理是不够的。


8
那个链接已经失效了。这是正确的链接:https://developer.apple.com/downloads/index.action?name=WWDC%202013。 - olivaresF
请注意,此示例代码需要 XCode 5.0 和 iOS SDK 7.0(尚未公开发布)。 - Mike Gledhill
谢谢提供修复后的链接,但是里面有几个示例代码,哪一个包含相关的UIImage类别? - Leonardo
doubleencore.com上他们说:“我们发现,在大多数情况下,10个点的模糊半径加上10个点的饱和度增加最能模仿iOS 7的模糊效果”。 - Jano
1
@Leonardo iOS_RunningWithASnap.zip - John Starr Dewar
1
...或者iOS_UIImageEffects.zip更具体地说就是这个。 - John Starr Dewar

11

这里是使用CIGaussianBlur在Swift中的快速实现:

func blur(image image: UIImage) -> UIImage {
    let radius: CGFloat = 20;
    let context = CIContext(options: nil);
    let inputImage = CIImage(CGImage: image.CGImage!);
    let filter = CIFilter(name: "CIGaussianBlur");
    filter?.setValue(inputImage, forKey: kCIInputImageKey);
    filter?.setValue("\(radius)", forKey:kCIInputRadiusKey);
    let result = filter?.valueForKey(kCIOutputImageKey) as! CIImage;
    let rect = CGRectMake(radius * 2, radius * 2, image.size.width - radius * 4, image.size.height - radius * 4)
    let cgImage = context.createCGImage(result, fromRect: rect);
    let returnImage = UIImage(CGImage: cgImage);

    return returnImage;
}

9
我认为最简单的解决方法是覆盖UIToolbar,它在iOS 7中会使其后面的所有东西都模糊。这很难察觉,但你可以很容易地实现它并且速度很快!
你可以对任何视图进行操作,只需将其设置为UIToolbar的子类而不是UIView即可。例如,你甚至可以对UIViewController的view属性进行操作...
1)创建一个新类,它是"UIViewController的子类",并勾选"带有用户界面的XIB"。
2)选择View并转到右侧面板中的身份检查器(alt-command-3)。将“Class”更改为UIToolbar。现在转到属性检查器(alt-command-4)并将“Background”颜色更改为“Clear Color”。
3)向主视图添加一个子视图,并将其连接到接口中的IBOutlet。将其称为backgroundColorView。作为实现(.m)文件中的私有类别,它看起来像这样。
@interface BlurExampleViewController ()
@property (weak, nonatomic) IBOutlet UIView *backgroundColorView;
@end

4) 前往视图控制器实现(.m)文件并更改-viewDidLoad方法,使其如下所示:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.view.barStyle = UIBarStyleBlack; // this will give a black blur as in the original post
    self.backgroundColorView.opaque = NO;
    self.backgroundColorView.alpha = 0.5;
    self.backgroundColorView.backgroundColor = [UIColor colorWithWhite:0.3 alpha:1];
}

这将为您提供一个深灰色的视图,可以模糊其后面的所有内容。没有任何奇怪的操作,也没有使用缓慢的核心图像模糊技术,而是利用操作系统/软件开发工具包(OS/SDK)提供的一切资源。
您可以按照以下方式将此视图控制器的视图添加到另一个视图中:
[self addChildViewController:self.blurViewController];
[self.view addSubview:self.blurViewController.view];
[self.blurViewController didMoveToParentViewController:self];

// animate the self.blurViewController into view

如果有不清楚的地方,请告诉我,我很乐意帮助!


编辑

在7.0.3中,UIToolbar已经更改,使用彩色模糊可能会产生不良影响。

我们以前可以使用barTintColor设置颜色,但如果您之前正在这样做,则需要将alpha组件设置为小于1。否则,您的UIToolbar将成为完全不透明的颜色-没有模糊效果。

可以通过以下方法实现:(请记住selfUIToolbar的子类)

UIColor *color = [UIColor blueColor]; // for example
self.barTintColor = [color colorWithAlphaComponent:0.5];

这将为模糊视图赋予蓝色调。

1
不错啊,我在我的视图中使用了这三行代码: self.backgroundColorView.opaque = NO; self.backgroundColorView.alpha = 0.5; self.backgroundColorView.backgroundColor = [UIColor colorWithWhite:0.3 alpha:1]; 但是背景并没有被模糊处理,只是产生了一个很好的覆盖效果。无论如何还是谢谢你! - IgniteCoders
1
我使用这个技术没有看到任何模糊效果,它只是创建了一个有色叠层。 - MusiGenesis
确保彩色叠加层的透明度小于1。您可以使用UIToolbar而无需视图控制器,这取决于您的需求可能更简单。 - Sam
不错的技巧。我在Storyboard中将我的视图转换为UIToolbar类,然后将视图背景更改为透明色。这样会产生一个白色模糊的背景。如果你将alpha值设为小于1,模糊效果就会消失。 - fullmoon

7

自定义模糊比例

您可以尝试使用自定义设置的UIVisualEffectView,如下所示 -

class BlurViewController: UIViewController {
    private let blurEffect = (NSClassFromString("_UICustomBlurEffect") as! UIBlurEffect.Type).init()

    override func viewDidLoad() {
        super.viewDidLoad()
        let blurView = UIVisualEffectView(frame: UIScreen.main.bounds)
        blurEffect.setValue(1, forKeyPath: "blurRadius")
        blurView.effect = blurEffect
        view.addSubview(blurView)
    }   
}

Output:对于 blurEffect.setValue (1...blurEffect.setValue (2... 这里是两张图片,你可以通过第一张图片第二张图片中的模糊效果看到结果。请注意,这些效果是通过在 iOS 中应用 Core Image 框架中的高斯滤镜(CIGaussianBlur)来实现的。

4
如果在下一个iOS版本中更改了此参数的名称,它将停止工作。 - Ariel Bogdziewicz
嗯...不,你永远不想访问私有API。它们是私有的原因。它们会改变,它们会出错,或者苹果将拒绝您的应用程序。使用另一种方法,有很多选择。感谢您发现这个黑客技巧,但不建议使用。 - n13
@Shaked 你遇到过任何编译器错误或运行时不反映的情况吗? - Jack
运行时 - setValueForKeyPath 在 tvOS 14 中导致应用程序崩溃。 - Shaked Sayag
在iOS14上,这会给我一个黑白未模糊的视图。 - w0mbat
显示剩余4条评论

6

如果有人需要,这是我基于Jordan H的答案创建的一个Swift扩展。它使用Swift 5编写,并且可以从Objective C中使用。

extension UIView {

    @objc func blurBackground(style: UIBlurEffect.Style, fallbackColor: UIColor) {
        if !UIAccessibility.isReduceTransparencyEnabled {
            self.backgroundColor = .clear

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

            self.insertSubview(blurEffectView, at: 0)
        } else {
            self.backgroundColor = fallbackColor
        }
    }

}

注意: 如果您想模糊UILabel的背景而不影响文本,则应创建容器UIView,将UILabel添加为其子视图,将UILabel的backgroundColor设置为UIColor.clear,然后在容器UIView上调用blurBackground(style:UIBlurEffect.Style, fallbackColor: UIColor)。这是一个使用Swift 5编写的快速示例:

let frame = CGRect(x: 50, y: 200, width: 200, height: 50)
let containerView = UIView(frame: frame)
let label = UILabel(frame: frame)
label.text = "Some Text"
label.backgroundColor = UIColor.clear
containerView.addSubview(label)
containerView.blurBackground(style: .dark, fallbackColor: UIColor.black)

请注意,当视图消失时,UIVisualEffectView 会经常崩溃。 - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接