原文链接: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
是调节黑白的地方。而 _UIVisualEffectBackdropView
的 layerClass 为:
@interface UICABackdropLayer : CABackdropLayer
@end
它做的事情其实也很简单,就是将它下面被它拦住的视图内容复制一份。真正的模糊效果是由之前一篇文章提到的 CALayer 的 filters 做的。
了解了原理之后,我们可以自定义一个类似 UIVisualEffectView 的视图,并且可以调节模糊程度,效果如下图:
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"];