/ IOS

iOS 圆角+阴影+离屏渲染问题

iOS 圆角+阴影+离屏渲染问题

在近一期开发需求中,有个控件需要同时添加圆角和阴影效果。本以为是个非常简单的需求,但开发过程并不十分美丽。今天就整理一下控件添加圆角和阴影一些需要注意的地方。

为什么给一个控件同时添加 阴影和圆角这么困难呢?一句话解释就是:

Because shadow is an effect done outside the View, and that masksToBounds set to YES will tell the UIView not to draw everything that is outside itself.

简单来说就是:阴影效果是展示在 View 外的,而切圆角要求不显示 View 外部的内容。显然,圆角和阴影要同时显示本身就是矛盾的。

就单个操作来说,给控件加圆角或者阴影的时候,如果操作不当,因为离屏渲染的问题,会导致比较严重的性能问题。尤其在流式布局如:UITableView、UICollectionView 上,如果添加多个带有阴影或圆角的子视图,性能损耗非常明显。

一种折中方案:直接让美工把图片切成圆角进行显示,这是效率最高的一种方案。
虽然图片的解析和展示会占用一部分内存,但是这是典型的用空间换取时间的情况,在优化时间有限的情况下,让美工做图是个不错的选择。

UIView、UILabel、UIImageView、UITextView 出现离屏渲染的条件存在略微差异,我们分开一一介绍。

优化

在离屏渲染无法避免的时候,那么退而求其次,让离屏渲染的代价尽量小。一些优化措施可以尽可能的避免性能损耗过大。

先说两个实现方案,然后针对离屏渲染问题进行优化:

方案1: 在 view 上显示圆角效果,再在底部插一个阴影的layer。 ``` UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 100, 100)]; bgView.backgroundColor = [UIColor brownColor];

bgView.layer.cornerRadius = 20.0f;
bgView.layer.masksToBounds = YES;

[self.view addSubview:bgView];

CALayer *subLayer=[CALayer layer];
CGRect fixframe = bgView.frame;
subLayer.frame= fixframe;
subLayer.cornerRadius=20;
subLayer.backgroundColor=[[UIColor blackColor] colorWithAlphaComponent:0.8].CGColor;
subLayer.masksToBounds=NO;
subLayer.shadowColor = [UIColor blackColor].CGColor;//shadowColor阴影颜色
subLayer.shadowOffset = CGSizeMake(3,2);//shadowOffset阴影偏移,x向右偏移3,y向下偏移2,默认(0, -3),这个跟shadowRadius配合使用
subLayer.shadowOpacity = 0.8;//阴影透明度,默认0
subLayer.shadowRadius = 4;//阴影半径,默认3
[self.view.layer insertSublayer:subLayer below:bgView.layer];
```

方案2: 不设置 layer.masksToBounds 。

UIView *v=[[UIView alloc]initWithFrame:CGRectMake(200, 100, 100, 100)]; v.backgroundColor=[UIColor brownColor]; //v.layer.masksToBounds=YES;不设置 v.layer.cornerRadius=20; v.layer.shadowColor=[UIColor blackColor].CGColor; v.layer.shadowOffset=CGSizeMake(5, 5); v.layer.shadowOpacity=0.5; v.layer.shadowRadius=5; [self.view addSubview:v];

实际上,让一个控件既实现shadowPath

下面的情况或操作会引发离屏渲染:

  • 为图层设置遮罩(layer.mask)
  • 为图层设置阴影(layer.shadow *)。
  • 为图层设置layer.shouldRasterize=true
  • 文本(任何种类,包括UILabel,CATextLayer,Core Text等)。
  • 将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
  • 将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
  • 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层
  • 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。

优化方案1:使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角

优化方案2:使用CAShapeLayer和UIBezierPath设置圆角

使用代码手动生成圆角Image设置到要显示的View上,利用UIBezierPath(CoreGraphics框架)画出来圆角图片