使用“带有模糊的视觉效果视图”减少模糊?

Posted

技术标签:

【中文标题】使用“带有模糊的视觉效果视图”减少模糊?【英文标题】:Less Blur with `Visual Effect View with Blur`? 【发布时间】:2015-06-12 11:31:56 【问题描述】:

标题几乎要求一切......

我正在使用 ios8 Visual Effect View with Blur。它在UIImageView 上方,显示用户可选择的背景照片。放置在 contentView 本身的视图是我自己的自定义视图,显示了一种图形/日历。大多数所说的日历图是透明的。它应用于它背后的照片的模糊真的很重。我想让更多的细节泄露出去。但苹果似乎只给出了三个罐头值:

typedef enum 
    UIBlurEffectStyleExtraLight,
    UIBlurEffectStyleLight,
    UIBlurEffectStyleDark 
 UIBlurEffectStyle;

我一直在使用 UIVisualEffectView 的不同 alpha 和背景颜色(尽管文档警告不要这样做),但这并没有做任何事情,只会让情况变得更糟。

【问题讨论】:

【参考方案1】:

很遗憾苹果没有提供任何模糊效果的选项。但这个解决方法对我有用 - 为模糊效果设置动画并在完成前暂停它。

func blurEffectView(enable enable: Bool) 
    let enabled = self.blurView.effect != nil
    guard enable != enabled else  return 

    switch enable 
    case true:
        let blurEffect = UIBlurEffect(style: .ExtraLight)
        UIView.animateWithDuration(1.5) 
            self.blurView.effect = blurEffect
        

        self.blurView.pauseAnimation(delay: 0.3)
    case false:
        self.blurView.resumeAnimation()

        UIView.animateWithDuration(0.1) 
            self.blurView.effect = nil
        
    

以及用于暂停(延迟)和恢复视图动画的 UIView 扩展

extension UIView 

    public func pauseAnimation(delay delay: Double) 
        let time = delay + CFAbsoluteTimeGetCurrent()
        let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, time, 0, 0, 0,  timer in
            let layer = self.layer
            let pausedTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil)
            layer.speed = 0.0
            layer.timeOffset = pausedTime
        )
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
    

    public func resumeAnimation() 
        let pausedTime  = layer.timeOffset

        layer.speed = 1.0
        layer.timeOffset = 0.0
        layer.beginTime = layer.convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
    

【讨论】:

对于 Apple 应该首先包含的内容,这是一个非常有创意的解决方案(尤其是因为它不使用私有 API)。 这可能有效,但并不完整。多次调用 blurEffectView 会导致多次调用 pauseAnimation 方法,这会在每次调用时添加一个新计时器并且不会删除它。而且,如果当前的run loop已经包含了一个runLoopTimer,CFRunLoopAddTimer就不会再添加一个新的定时器了。 似乎不再起作用了——或者至少对我来说不起作用。我只是没有得到任何模糊效果。对于所有这些解决方案,我要么完全没有模糊,要么在此处其他地方提到崩溃。【参考方案2】:

您得到严重模糊的原因是模糊效果样式会影响图像的亮度级别,而不是应用的模糊量。

不幸的是,尽管 Apple 显然有能力以编程方式控制应用的模糊量 - 尝试在启动板上缓慢向下拖动以观察 Spotlight 模糊过渡 - 我没有看到任何公共 API 可以将模糊量传递给UIBlurEffect.

这篇文章声称调整背景颜色 alpha 将驱动模糊量。值得一试,但我看不到记录在哪里:How to fade a UIVisualEffectView and/or UIBlurEffect in and out?

【讨论】:

我只是建议您不要更改 alpha 值。正如 Apple 所说:“使用 UIVisualEffectView 类时,请避免小于 1 的 alpha 值。(...) UIVisualEffectView 对象需要组合为它们所叠加的内容的一部分,以便看起来正确。设置视觉效果视图或其任何超级视图上小于 1 的 alpha 会导致许多效果看起来不正确或根本不显示。”参考:developer.apple.com/library/ios/documentation/UIKit/Reference/… 嗨 @stilesCrisis 你能帮我解决这个问题吗 正是我想要了解为什么我的模糊不是模糊的原因。谢谢! 只要看看这个,还有更多的模糊样式。我发现 .systemUltraThinMaterial 效果相当不错: 调整 alpha 只会让您看起来像是丢了一只隐形眼镜。您将看到模糊的图像叠加在其后面仍然清晰的图像之上。我认为这不是任何人想要的。【参考方案3】:

这对我有用。

在添加到我的视图之前,我将UIVisualEffectView 放在UIView 中。

我让这个功能更容易使用。您可以使用此功能对视图中的任何区域进行模糊处理。

func addBlurArea(area: CGRect, style: UIBlurEffectStyle) 
    let effect = UIBlurEffect(style: style)
    let blurView = UIVisualEffectView(effect: effect)

    let container = UIView(frame: area)
    blurView.frame = CGRect(x: 0, y: 0, width: area.width, height: area.height)
    container.addSubview(blurView)
    container.alpha = 0.8
    self.view.insertSubview(container, atIndex: 1)

例如,您可以通过调用使所有视图模糊:

addBlurArea(self.view.frame, style: UIBlurEffectStyle.Dark)

您可以将Dark 更改为您想要的模糊样式,将0.8 更改为您想要的alpha 值

【讨论】:

这完全消除了模糊。 它现在仍然对我有用。您可以查看 iOS 版本、XCode 版本...我当前的版本:iOS 9.2.1 (13D15) XCode 7.2 beta (7C62b) 我逐字复制了你的代码,它产生了同样的结果:一个没有模糊的部分不透明视图的视图,我想这在技术上是“不那么模糊”。 iOS 9.2 (13C75) Xcode 7.2.1 (7C1002) 我意识到,在我将 blurView 添加到 containerView 后,视图不再保持模糊,我还缺少什么吗? 不要使用 alpha,如果苹果建议不要做某事,那值得一试。【参考方案4】:

与此处的一些类似但更简单的解决方案是使用 UIViewPropertyAnimator (iOS 10+) 并将其 fractionComplete 属性设置为介于 0 和 1 之间的某个值。

    // add blur view to image view
    let imgBlur = UIVisualEffectView()
    imgView.addSubview(imgBlur)
    imgBlur.frame = imgView.bounds

    // create animator to control blur strength
    let imgBlurAnimator = UIViewPropertyAnimator()
    imgBlurAnimator.addAnimations 
        imgBlur.effect = UIBlurEffect(style: .dark)
    

    // 50% blur
    imgBlurAnimator.fractionComplete = 0.5

请注意,如果您打算根据平移手势、滚动视图、滑块等更改 fractionComplete,则需要设置 pausesOnCompletion = true (iOS 11+)。

【讨论】:

我收到此错误“*** 由于未捕获的异常 'NSInternalInconsistencyException' 而终止应用程序,原因:'释放暂停或停止的属性动画师是错误的。属性动画师必须完成动画或在它们被释放之前明确停止并完成。'" 需要添加[pa stopAnimation:YES]; [pa finishAnimationAtPosition:UIViewAnimatingPositionCurrent];但它并没有改变模糊值 在设置 imgBlurAnimator.stopAnimation(true) 时不适用于 iOS 13 Beta。没有模糊效果。【参考方案5】:

类似于@mitja13 的解决方案,但使用UIViewPropertyAnimator,稍微简洁一些:

var animator: UIViewPropertyAnimator!

viewDidLoad() 
   super.viewDidLoad()

   let blurEffectView = UIVisualEffectView()
   yourViewToBeBlurred.addSubview(blurEffectView)
   blurEffectView.fillSuperview() // This my custom method that anchors to the superview using auto layout. Write your own

   animator = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: 
       blurEffectView.effect = UIBlurEffect(style: .regular)
   )

   animator.fractionComplete = 0.6 // Adjust to the level of blur you want
 

【讨论】:

我只是为了测试而测试它。漂亮干净的解决方案。谢谢。投票赞成【参考方案6】:

您可以在图像上添加 UIBlurEffect。这样就可以了。

这是一个带有模糊效果的 UIImageView 示例。记得给 UIImageView 添加一个 Image。

使用 blurEffectView.alpha = 0.8(从 0 到 1)调整模糊量

import UIKit

class BlurEffectImageView: UIImageView 

override func awakeFromNib() 
    super.awakeFromNib()
    addBlurEffect()


private func addBlurEffect() 
    let blurEffect = UIBlurEffect(style: .light)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    blurEffectView.alpha = 0.8
    
    blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    
    blurEffectView.translatesAutoresizingMaskIntoConstraints = false
    addSubview(blurEffectView)
    
    NSLayoutConstraint(item: blurEffectView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0).isActive = true
    NSLayoutConstraint(item: blurEffectView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true
    NSLayoutConstraint(item: blurEffectView, attribute: .height,  relatedBy: .equal, toItem: self, attribute: .height,  multiplier: 1.0, constant: 0).isActive = true
    NSLayoutConstraint(item: blurEffectView, attribute: .width,   relatedBy: .equal, toItem: self, attribute: .width,   multiplier: 1.0, constant: 0).isActive = true
  


【讨论】:

这也完全消除了模糊效果。 我不确定你的意思。这个想法只是添加模糊效果,“完全删除”是什么意思? @alegelos - 这意味着它背后的图像不再模糊。我发现效果与您使用标准 UIView 的效果相同,例如带有 alpha 集的黑色背景。内容可见但变暗;但是,它并没有模糊。【参考方案7】:

将 BlurEffectView 添加到具有视图 alpha

func addBlurEffectView() -> Void 
    if !UIAccessibilityIsReduceTransparencyEnabled() 
        let viewContainer = UIView()
        viewContainer.frame = self.view.bounds
        viewContainer.alpha = 0.5

        let blurEffect = UIBlurEffect(style: .dark)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.layer.zPosition = -0.5;
        blurEffectView.frame = self.view.bounds;
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        viewContainer.addSubview(blurEffectView)

        self.view.addSubview(viewContainer)
        self.view.sendSubview(toBack: viewContainer)
    

【讨论】:

【参考方案8】:

我像这样使用 UIVisualEffectView 来获得可调节的模糊圆圈。模糊级别由控制 Alpha 的滑块控制。我也会在下面包含滑块处理程序。模糊圆圈的大小可通过捏散动作进行调整。我也会包括在内。你可以拖动模糊圈。我将把它作为练习留给读者。如果您想要一个模糊矩形,请不要圆角。要查看这个模糊圆圈设计的实际效果,请加载 MemeSoEasy 应用(免费),添加一张照片(您可以在上面放置一个模糊圆圈),然后添加一个模糊圆圈。

UIVisualEffectView *blurVisualEffectView;

UIVisualEffect *blurEffect;
blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
blurVisualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurVisualEffectView.frame = lastChosenBlurCircleRect;
blurVisualEffectView.center = CGPointMake(halfScreenX, halfScreenY);
[self.view addSubview:blurVisualEffectView];
CGFloat blurCornerRadius = blurVisualEffectView.bounds.size.width/2;
[[blurVisualEffectView layer]setCornerRadius:blurCornerRadius];
[[blurVisualEffectView layer]setMasksToBounds:YES];
[[blurVisualEffectView layer] setBorderWidth:4.0f];
[[blurVisualEffectView layer] setBorderColor:[UIColor blueColor].CGColor];
blurVisualEffectView.userInteractionEnabled = NO;
blurVisualEffectView.alpha = 0.97;
[blurArray addObject:blurVisualEffectView];

滑块处理程序:

请注意,我将模糊对象存储在一个数组中,因此我可以让用户创建任意数量的对象。滑块处理程序适用于数组中的最后一个对象。滑块的最小值和最大值分别为 0.0 和 1.0

UISlider *slider_ = (UISlider *)sender;
CGFloat ourSliderValue = slider_.value;
UIVisualEffectView *currentBlurObject =
[blurArray objectAtIndex:blurArray.count - 1];
currentBlurObject.alpha = ourSliderValue;

捏散的大小更改处理程序

int changeInWidth = 0; // one pixel at a time

if (pinchGesture.scale > 1.0) 
    changeInWidth++;

if (pinchGesture.scale < 1.0) 
    changeInWidth--;


UIVisualEffectView *currentBlurObject =
[blurArray objectAtIndex:blurArray.count - 1];

CGPoint oldCenter = currentBlurObject.center;

currentBlurObject.frame = CGRectMake(0, 0, currentBlurObject.frame.size.width + changeInWidth, currentBlurObject.frame.size.width + changeInWidth);

currentBlurObject.center = oldCenter;

lastChosenBlurCircleRect = currentBlurObject.frame;

CGFloat blurCornerRadius = currentBlurObject.frame.size.width/2;
[[currentBlurObject layer]setCornerRadius:blurCornerRadius];

【讨论】:

【参考方案9】:

为了使用模糊级别的模糊 - 使用下面的我的扩展:

public extension UIView 
  func applyBlur(level: CGFloat) 
    let context = CIContext(options: nil)
    self.makeBlurredImage(with: level, context: context, completed:  processedImage in
      let imageView = UIImageView(image: processedImage)
      imageView.translatesAutoresizingMaskIntoConstraints = false
      self.addSubview(imageView)
      NSLayoutConstraint.activate([
        imageView.topAnchor.constraint(equalTo: self.topAnchor),
        imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
        imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
        imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
      ])
    )
  

  private func makeBlurredImage(with level: CGFloat, context: CIContext, completed: @escaping (UIImage) -> Void) 
    // screen shot
    UIGraphicsBeginImageContextWithOptions(self.frame.size, false, 1)
    self.layer.render(in: UIGraphicsGetCurrentContext()!)
    let resultImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    let beginImage = CIImage(image: resultImage)

    // make blur
    let blurFilter = CIFilter(name: "CIGaussianBlur")!
    blurFilter.setValue(beginImage, forKey: kCIInputImageKey)
    blurFilter.setValue(level, forKey: kCIInputRadiusKey)

    // extend source image na apply blur to it
    let cropFilter = CIFilter(name: "CICrop")!
    cropFilter.setValue(blurFilter.outputImage, forKey: kCIInputImageKey)
    cropFilter.setValue(CIVector(cgRect: beginImage!.extent), forKey: "inputRectangle")

    let output = cropFilter.outputImage
    var cgimg: CGImage?
    var extent: CGRect?

    let global = DispatchQueue.global(qos: .userInteractive)

    global.async 
      extent = output!.extent
      cgimg = context.createCGImage(output!, from: extent!)!
      let processedImage = UIImage(cgImage: cgimg!)

      DispatchQueue.main.async 
        completed(processedImage)
      
    
  

如何使用。如果视图已经完成,则在框架中运行它。例如在 viewDidAppear 中:

override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)

    myView.applyBlur(level: 5)

【讨论】:

不错的答案。 (已投票。)不过,我看到的一个问题是原始图像的内容何时发生变化。您无法删除和替换旧的模糊视图。也许在模糊视图上使用标签,并在添加另一个视图之前使用它来查找删除任何以前的视图?或者,您可以将模糊作为关联值附加到视图(尽管这不是很 Swifty)【参考方案10】:

此答案基于Mitja Semolic's excellent earlier answer。我已将其转换为 swift 3,在评论中添加了对正在发生的事情的解释,使其成为 UIViewController 的扩展,因此任何 VC 都可以随意调用它,添加了一个不模糊的视图以显示选择性应用程序,并添加了一个完成块,以便调用视图控制器可以在完成模糊时做任何事情。

    import UIKit
//This extension implements a blur to the entire screen, puts up a HUD and then waits and dismisses the view.
    extension UIViewController 
        func blurAndShowHUD(duration: Double, message: String, completion: @escaping () -> Void)  //with completion block
            //1. Create the blur effect & the view it will occupy
            let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
            let blurEffectView = UIVisualEffectView()//(effect: blurEffect)
            blurEffectView.frame = self.view.bounds
            blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        //2. Add the effect view to the main view
            self.view.addSubview(blurEffectView)
        //3. Create the hud and add it to the main view
        let hud = HudView.getHUD(view: self.view, withMessage: message)
        self.view.addSubview(hud)
        //4. Begin applying the blur effect to the effect view
        UIView.animate(withDuration: 0.01, animations: 
            blurEffectView.effect = blurEffect
        )
        //5. Halt the blur effects application to achieve the desired blur radius
        self.view.pauseAnimationsInThisView(delay: 0.004)
        //6. Remove the view (& the HUD) after the completion of the duration
        DispatchQueue.main.asyncAfter(deadline: .now() + duration) 
            blurEffectView.removeFromSuperview()
            hud.removeFromSuperview()
            self.view.resumeAnimationsInThisView()
            completion()
        
    


extension UIView 
    public func pauseAnimationsInThisView(delay: Double) 
        let time = delay + CFAbsoluteTimeGetCurrent()
        let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, time, 0, 0, 0,  timer in
            let layer = self.layer
            let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
            layer.speed = 0.0
            layer.timeOffset = pausedTime
        )
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
    
    public func resumeAnimationsInThisView() 
        let pausedTime  = layer.timeOffset

        layer.speed = 1.0
        layer.timeOffset = 0.0
        layer.beginTime = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    

我已确认它适用于 iOS 10.3.1 和 iOS 11

【讨论】:

【参考方案11】:

非常感谢mitja13,我制作了 Objective-C 版本。

NS_ASSUME_NONNULL_BEGIN

@interface UIView (Gaoding)

- (void)gd_pauseAnimationsWithDelay:(double)delay;
- (void)gd_resumeAnimations;

@end

NS_ASSUME_NONNULL_END

@implementation UIView (Gaoding)

- (void)gd_pauseAnimationsWithDelay:(double)delay 
    double time = delay + CFAbsoluteTimeGetCurrent();
    __block CALayer *layer = self.layer;

    CFRunLoopRef runloopRef = CFRunLoopGetCurrent();
    CFRunLoopAddTimer(runloopRef, CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, time, 0, 0, 0, ^(CFRunLoopTimerRef timer) 
        double pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
        layer.speed = 0;
        layer.timeOffset = pausedTime;
        layer = nil;
        CFRunLoopRemoveTimer(runloopRef, timer, kCFRunLoopCommonModes);
        CFRelease(timer);
        timer = NULL;
    ), kCFRunLoopCommonModes);


- (void)gd_resumeAnimations 
    CALayer *layer = self.layer;
    double pausedTime = layer.timeOffset;
    layer.speed = 1;
    layer.timeOffset = 0.0;
    layer.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;


@end

如何使用:

/// SHOW IT

UIVisualEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurEffectView = UIVisualEffectView.new;

// .... something other

[UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^
    blurEffectView.effect = effect;
];
[blurEffectView gd_pauseAnimationsWithDelay:0.1]; // 0.1/0.35 = 28.57% blur of UIBlurEffectStyleLight

// .... something other

/// HIDE IT
[blurEffectView gd_resumeAnimations];
[UIView animateWithDuration:0.35 delay:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState animations:^
    blurEffectView.effect = nil;
];

【讨论】:

以上是关于使用“带有模糊的视觉效果视图”减少模糊?的主要内容,如果未能解决你的问题,请参考以下文章

巧用模糊实现视觉的 3D 效果

为啥视觉工作室模糊字体

模糊的视觉效果视图变成深灰色

视觉效果上的文字模糊

使用 imagemagick 减少图像宽度和高度而不模糊

Python深度学习:计算机视觉处理库OpenCVNumpy编辑图片高斯模糊处理(读书笔记)