如何编辑UIBlurEffect的强度?

31

我不希望我的背景图片太模糊。有没有一种属性可以调整模糊程度?

let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
blurEffect.???
let effectView = UIVisualEffectView(effect: blurEffect)
effectView.frame = backgroundAlbumCover.bounds
backgroundAlbumCover.addSubview(effectView)

嘿,Christos。有没有可能再审核一下答案,也许会有更合适的答案可以标记为解决方案? - bodich
6个回答

30

使用动画器可以以非常优雅的方式执行此操作

(减少UIVisualEffectView的alpha值不会影响模糊程度,因此我们必须使用动画器)

使用方法就像这样简单:

let blurEffectView = BlurEffectView()
view.addSubview(blurEffectView)

模糊效果视图实现:

class BlurEffectView: UIVisualEffectView {
    
    var animator = UIViewPropertyAnimator(duration: 1, curve: .linear)
    
    override func didMoveToSuperview() {
        guard let superview = superview else { return }
        backgroundColor = .clear
        frame = superview.bounds //Or setup constraints instead
        setupBlur()
    }
    
    private func setupBlur() {
        animator.stopAnimation(true)
        effect = nil

        animator.addAnimations { [weak self] in
            self?.effect = UIBlurEffect(style: .dark)
        }
        animator.fractionComplete = 0.1   //This is your blur intensity in range 0 - 1
    }
    
    deinit {
        animator.stopAnimation(true)
    }
}

2
看起来是个好主意,但我在这边的项目上无法让它工作。每次相关视图加载时都会崩溃。线程1:异常:“释放已暂停或停止的属性动画器是错误的。属性动画器必须在完成动画或明确停止和完成之前才能被释放。” - Dave Y
1
Dave,看起来你在消息发布之前没有停止它。可能是你添加动画器的类已经被释放了。尝试显式添加deinit并检查。你没有模糊问题,你在某个地方使用动画器的方式不正确。 - bodich
1
你的程序崩溃的原因是因为代码试图摆脱动画器。在类的顶部声明它,这样它就有了与类相同的时间跨度。 - TDesign
1
@AabanTariqMurtaza 当然可以。因为这个话题是关于管理模糊强度,而不是透明度。 - bodich
@AabanTariqMurtaza Intensity在我的两个项目和其他人的项目中都表现得非常好。你可以在这里分享一个简单的示例项目(例如github),只有两个视图(一个在另一个下面),我会看一下为什么你没有得到工作强度。 - bodich
显示剩余8条评论

23

模糊本身无法调整...但是,您可以调整模糊视图的可见度。这可以通过多种方式完成,目前我只能想到其中三种:

第一种选择:调整UIVisualEffectView实例的alpha,如:

effectView.alpha = 0.4f;

第二个选项: 在 effectView 的索引 0 上添加一个 UIView 实例并调整此 UIView 实例的 alpha 值。例如:

UIView *blurDilutionView = [[UIView alloc] initWithFrame: effectView.frame];
blurDilutionView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent: 0.5];
blurDilutionView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;//if not using AutoLayout
[effectView insertSubview:blurDilutionView atIndex:0];

第三种方法:使用多个UIVisualEffectView实例(我还没有尝试过,更像是一个想法)。每个实例应用0.1f的alpha。你拥有的UIVisualEffectView越多,整体效果就会更模糊。再次声明,我还没有尝试这个选项!

更新:正如Axeva在评论中提到的那样,苹果建议不要调整alpha来改变模糊度。所以请自己考虑使用这些建议时可能带来的风险。


请告知您是否需要这些建议的Swift替代方案。 - pnizzle
12
苹果公司明确建议不要使用alpha来控制模糊效果。在使用UIVisualEffectView类时,应避免使用小于1的alpha值。将可视化效果视图或其任何父视图的alpha设置为小于1会导致许多效果看起来不正确或根本不显示。 - Axeva
3
你无法通过透明度来控制效果的强度,你只能在效果下开始部分地看到完全清晰的视图。 - bodich
是的,这些“解决方案”是 Apple 不建议使用的。但是它们并非完全被禁止,在某些情况下,由于其不正确的外观效果正是某些人所追求的。并非所有的应用程序都适用于公共 AppStore。有些是为个人和私人分发目的而设计的。因此,我们不能因为 Apple 建议不要使用这些方法而选择不考虑解决办法。 - pnizzle

7

我曾经遇到一个问题,需要创建一个比.light更暗,比.dark更亮的UIBlurEffect样式的模糊效果。

为了实现这个效果,在背面放置一个具有所需颜色和透明度的视图:

    let pictureImageView = // Image that you need to blur
    let backView = UIView(frame: pictureImageView.bounds)
    backView.backgroundColor = UIColor(red: 100/255, green: 100/255, blue: 100/255, alpha: 0.3)
    pictureImageView.addSubview(backView)

    let blurEffect = UIBlurEffect(style: .light)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    blurEffectView.frame = pictureImageView.bounds
    pictureImageView.addSubview(blurEffectView)

结果如下所示:

enter image description here

要了解更多详细信息,请查看此文章更新: 显然还有另一种不错(甚至可能更好)的方法来使用CIFilter(name: “CIGaussianBlur”)实现模糊效果。 它允许使“不透明度”和模糊强度远低于UIBlurEffect。

3
如果需要的话可以使用私有API。在iOS 13.7、14.8、15.5和16.0上进行了测试,但不支持Mac Catalyst。

示例

  • UIVisualEffectView+Intensity.h
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIVisualEffectView (Intensity)
@property (nonatomic) CGFloat intensity;
@end

NS_ASSUME_NONNULL_END
  • UIVisualEffectView+Intensity.m
#import "UIVisualEffectView+Intensity.h"
#import <objc/message.h>

@interface UIVisualEffectView (Intensity)
@property (readonly) id backgroundHost; // _UIVisualEffectHost
@property (readonly) __kindof UIView *backdropView; // _UIVisualEffectBackdropView
@end

@implementation UIVisualEffectView (Intensity)

- (id)backgroundHost {
    id backgroundHost = ((id (*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(@"_backgroundHost")); // _UIVisualEffectHost
    return backgroundHost;
}

- (__kindof UIView * _Nullable)backdropView {
    __kindof UIView *backdropView = ((__kindof UIView * (*)(id, SEL))objc_msgSend)(self.backgroundHost, NSSelectorFromString(@"contentView")); // _UIVisualEffectBackdropView
    return backdropView;
}

- (CGFloat)intensity {
    __kindof UIView *backdropView = self.backdropView; // _UIVisualEffectBackdropView
    __kindof CALayer *backdropLayer = ((__kindof CALayer * (*)(id, SEL))objc_msgSend)(backdropView, NSSelectorFromString(@"backdropLayer")); // UICABackdropLayer
    
    NSArray *filters = backdropLayer.filters;
    id _Nullable __block gaussianBlur = nil; // CAFilter
    
    [filters enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (![obj respondsToSelector:NSSelectorFromString(@"type")]) return;
        
        NSString *type = ((NSString * (*)(id, SEL))objc_msgSend)(obj, NSSelectorFromString(@"type"));
        
        if (![type isKindOfClass:[NSString class]]) return;
        
        if ([type isEqualToString:@"gaussianBlur"]) {
            gaussianBlur = obj;
            *stop = YES;
        }
    }];
    
    if (gaussianBlur == nil) return 0.0f;
    
    NSNumber * _Nullable inputRadius = [gaussianBlur valueForKeyPath:@"inputRadius"];
    
    if ((inputRadius == nil) || (![inputRadius isKindOfClass:[NSNumber class]])) return 0.0f;
    
    return [inputRadius floatValue];
}

- (void)setIntensity:(CGFloat)intensity {
    id descriptor = ((id (*)(id, SEL, id, BOOL))objc_msgSend)(self, NSSelectorFromString(@"_effectDescriptorForEffects:usage:"), @[self.effect], YES); // _UIVisualEffectDescriptor
    
    NSArray *filterEntries = ((NSArray * (*)(id, SEL))objc_msgSend)(descriptor, NSSelectorFromString(@"filterEntries")); // NSArray<_UIVisualEffectFilterEntry *>
    
    id _Nullable __block gaussianBlur = nil; // _UIVisualEffectFilterEntry
    
    [filterEntries enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSString *filterType = ((NSString * (*)(id, SEL))objc_msgSend)(obj, NSSelectorFromString(@"filterType"));
        
        if ([filterType isEqualToString:@"gaussianBlur"]) {
            gaussianBlur = obj;
            *stop = YES;
        }
    }];
    
    if (gaussianBlur == nil) return;
    
    NSMutableDictionary *requestedValues = [((NSDictionary * (*)(id, SEL))objc_msgSend)(gaussianBlur, NSSelectorFromString(@"requestedValues")) mutableCopy];

    if (![requestedValues.allKeys containsObject:@"inputRadius"]) {
        NSLog(@"Not supported effect.");
        return;
    }

    requestedValues[@"inputRadius"] = [NSNumber numberWithFloat:intensity];

    ((void (*)(id, SEL, NSDictionary *))objc_msgSend)(gaussianBlur, NSSelectorFromString(@"setRequestedValues:"), requestedValues);
    
    ((void (*)(id, SEL, id))objc_msgSend)(self.backgroundHost, NSSelectorFromString(@"setCurrentEffectDescriptor:"), descriptor);
    
    ((void (*)(id, SEL))objc_msgSend)(self.backdropView, NSSelectorFromString(@"applyRequestedFilterEffects"));
}

@end
  • Usage
let firstBlurView: UIVisualEffectView = .init(effect: UIBlurEffect(style: .dark))

// setter
firstBlurView.intensity = 7

// getter
print(firstBlurView.intensity) // 7.0


太棒了!非常感谢你制作这个!❤️ - iangilman
这个扩展能用于Swift吗? - Rashesh Bosamiya
能否用Swift重新创建这个? - undefined

1
这是BlurEffectView类,具有公共强度设置器,并符合Apple的UIView.animation函数(您可以通过UIKit的动画来动态调整强度)。

BlurEffectView.swift

import UIKit

public class BlurEffectView: UIView {

public override class var layerClass: AnyClass {
    return BlurIntensityLayer.self
}

@objc
@IBInspectable
public dynamic var intensity: CGFloat {
    set { self.blurIntensityLayer.intensity = newValue }
    get { return self.blurIntensityLayer.intensity }
}
@IBInspectable
public var effect = UIBlurEffect(style: .dark) {
    didSet {
        self.setupPropertyAnimator()
    }
}
private let visualEffectView = UIVisualEffectView(effect: nil)
private var propertyAnimator: UIViewPropertyAnimator!
private var blurIntensityLayer: BlurIntensityLayer {
    return self.layer as! BlurIntensityLayer
}

public override init(frame: CGRect) {
    super.init(frame: frame)
    self.setupView()
}

public required init?(coder: NSCoder) {
    super.init(coder: coder)
    self.setupView()
}

deinit {
    self.propertyAnimator.stopAnimation(true)
}

private func setupPropertyAnimator() {
    self.propertyAnimator?.stopAnimation(true)
    self.propertyAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
    self.propertyAnimator.addAnimations { [weak self] in
        self?.visualEffectView.effect = self?.effect
    }
    self.propertyAnimator.pausesOnCompletion = true
}
  
private func setupView() {
    self.backgroundColor = .clear
    self.isUserInteractionEnabled = false
    
    self.addSubview(self.visualEffectView)
    self.visualEffectView.fill(view: self)
    self.setupPropertyAnimator()
}

public override func display(_ layer: CALayer) {
    guard let presentationLayer = layer.presentation() as? BlurIntensityLayer else {
        return
    }
    let clampedIntensity = max(0.0, min(1.0, presentationLayer.intensity))
    self.propertyAnimator.fractionComplete = clampedIntensity
}
}

BlurIntensityLayer.swift

import QuartzCore

class BlurIntensityLayer: CALayer {

@NSManaged var intensity: CGFloat

override init(layer: Any) {
    super.init(layer: layer)
    
    if let layer = layer as? BlurIntensityLayer {
        self.intensity = layer.intensity
    }
}

override init() {
    super.init()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override class func needsDisplay(forKey key: String) -> Bool {
    key == #keyPath(intensity) ? true : super.needsDisplay(forKey: key)
}

override func action(forKey event: String) -> CAAction? {
    guard event == #keyPath(intensity) else {
        return super.action(forKey: event)
    }
    
    let animation = CABasicAnimation(keyPath: event)
    animation.toValue = nil
    animation.fromValue = (self.presentation() ?? self).intensity
    return animation
}
}

这绝对是唯一一个有效的(而且还带有@IBInspectable!) - yuji
这绝对是唯一一个有效的(而且还带有@IBInspectable!) - undefined

1

UIBlurEffect没有提供这样的属性。如果你想要其他强度,你将不得不自己制作一个BlurEffect。


你有任何想法吗,Christian? - ton
@ton 请查看我发布的建议。可能有些晚,但希望能帮助其他人。 - pnizzle

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