美文网首页Xcoder Tips
UIVisualEffectView 背后的实现

UIVisualEffectView 背后的实现

作者: rickytan | 来源:发表于2021-06-08 22:57 被阅读0次

    原文链接:https://xcoder.tips/behind-uivisualeffectview/

    iOS 8 苹果为我们带来了原生的毛玻璃效果的支持,即 UIVisualEffectView

    UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
    

    但它提供的 API 非常有限,能改的样式属性只有两个 effect 以及不多的几个 style,模糊效果也基本是非黑即白,模糊程度也不可调节。但有时候不关心实现的设计师们会要求某些地方模糊小一点之类的,此时一般就换自己用高斯模糊来做了,这里问题就来了,真的做不了吗?

    我们可以通过一些视图审查工具(如:Lookin)发现 UIVisualEffectView 背后其实由三个视图构成:

    • _UIVisualEffectBackdropView
    • _UIVisualEffectSubview
    • _UIVisualEffectContentView

    其中,_UIVisualEffectBackdropView 是真正产生模糊效果的地方,_UIVisualEffectSubview 是调节黑白的地方。而 _UIVisualEffectBackdropViewlayerClass 为:

    @interface UICABackdropLayer : CABackdropLayer
    @end
    

    它做的事情其实也很简单,就是将它下面被它拦住的视图内容复制一份。真正的模糊效果是由之前一篇文章提到的 CALayer 的 filters 做的。

    了解了原理之后,我们可以自定义一个类似 UIVisualEffectView 的视图,并且可以调节模糊程度,效果如下图:

    image.png image.png
    IB_DESIGNABLE
    @interface RTBackdropView : UIView
    @property (nonatomic) IBInspectable CGFloat blurRadius;
    @property (nonatomic) IBInspectable CGFloat saturation;
    @end
    
    @implementation RTBackdropView
    
    + (Class)layerClass
    {
        return NSClassFromString(@"CABackdropLayer");
    }
    
    - (instancetype)initWithCoder:(NSCoder *)coder
    {
        self = [super initWithCoder:coder];
        if (self) {
            self.blurRadius = 30;
            self.saturation = 2;
        }
        return self;
    }
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            self.blurRadius = 30;
            self.saturation = 2;
        }
        return self;
    }
    
    - (void)setBlurRadius:(CGFloat)blurRadius
    {
        if (_blurRadius != blurRadius) {
            _blurRadius = blurRadius;
            [self _updateFilters];
        }
    }
    
    - (void)setSaturation:(CGFloat)saturation
    {
        if (_saturation != saturation) {
            _saturation = saturation;
            [self _updateFilters];
        }
    }
    
    - (void)_updateFilters {
        self.layer.filters = @[
            ({
                CIFilter *sat = [NSClassFromString(@"CAFilter") filterWithName:@"colorSaturate"];
                [sat setValue:@(self.saturation) forKey:@"inputAmount"];
                [sat setValue:@YES forKey:@"inputNormalizeEdges"];
                sat;
            }),
            ({
                CIFilter *blur = [NSClassFromString(@"CAFilter") filterWithName:@"gaussianBlur"];
                blur.name = @"blur";    // 注意这个名字,后面有用!
                [blur setValue:@(self.blurRadius) forKey:@"inputRadius"];
                blur;
            }),
        ];
    }
    
    @end
    

    这样就完了吗?我们还可以加动画呢!

        // 下面的 .blur 是 filter 的 name
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"filters.blur.inputRadius"];
        animation.fromValue = @0;
        animation.toValue = @20;
        animation.duration = 0.5;
        animation.autoreverses = YES;
        animation.removedOnCompletion = NO;
        animation.repeatCount = FLT_MAX;
        [view.layer addAnimation:animation forKey:@"Blur"];
    
    blur-anim.gif

    相关文章

      网友评论

        本文标题:UIVisualEffectView 背后的实现

        本文链接:https://www.haomeiwen.com/subject/atkseltx.html