UIVisualEffectView 背后的实现

原文链接: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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 12,720评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 11,342评论 4 8
  • 步骤:发微博01-导航栏内容 -> 发微博02-自定义TextView -> 发微博03-完善TextView和...
    dibadalu阅读 8,343评论 1 3
  • 回这一趟老家,心里多了两个疙瘩。第一是堂姐现在谈了一个有妇之夫,在她的语言中感觉,她不打算跟他有太长远的计划,这让...
    安九阅读 8,875评论 2 4