iOS-高效设置圆角

一、前因

CALayer由背景色backgroundColor、内容contents、边缘borderWidth&borderColor构成

  • 设置圆角不就是设置layer的cornerRadius吗,还谈什么高效?
    因为这个属性只会影响视图的背景颜色和 border。所以该方法只对UIView有效,对于 UIImageView 这样内部还有子视图的控件就无能为力了。
  • 所以很多情况下我们会加上layer.masksToBounds的设置。

这样圆角效果就有了。但是,如果你勾选上 Color Offscreen-Rendered Yellow,就会发现 label 的四周出现了黄色的标记,说明这里出现了离屏渲染。关于离屏渲染的介绍,可以参考:UIKit性能调优实战讲解

之前有的文章说 iOS 9 做了什么特殊优化,或者是离屏渲染的影响不大,其主要原因在于圆角不够多。当我将一个 UIImageView 也设置成圆角,也就是屏幕上的圆角视图达到 34 个时,fps 大幅度下降,大约只有 33 左右。基本上已经达到了影响用户体验的范围。因此,一切不讲依据的优化都是耍流氓,如果你的圆角视图不多,cell 不复杂,就不要费力气折腾了。

二、首先,来个错误示范:

override func drawRect(rect: CGRect) {  
    let maskPath = UIBezierPath(roundedRect: rect,
                                byRoundingCorners: .AllCorners,
                                cornerRadii: CGSize(width: 3, height: 3))
    let maskLayer = CAShapeLayer()
    maskLayer.frame = self.bounds
    maskLayer.path = maskPath.CGPath
    self.layer.mask = maskLayer
}
  • 首先,我们应该尽量避免重写 drawRect
    方法。不恰当的使用这个方法会导致内存暴增。举个例子,iPhone6 上与屏幕等大的 UIView
    ,即使重写一个空的 drawRect
    方法,它也至少占用 750 * 1134 * 4 字节 ≈ 3.4 Mb
    的内存。在 内存恶鬼drawRect 及其后续中,作者详细介绍了其中原理,据他测试,在 iPhone6 上空的、与屏幕等大的视图重写 drawRect
    方法会消耗 5.2 Mb 内存。总之,能避免重写 drawRect
    方法就尽可能避免。
  • 其次,这种方法本质上是用遮罩层 mask
    来实现,因此同样无可避免的会导致离屏渲染。我试着将此前 34 个视图的圆角改用这种方法实现,结果 fps 掉到 11 左右。已经属于卡出翔的节奏了。

三、实战:设置圆角的正确姿势

1.UIView设置圆角

对于 contents 无内容或者内容的背景透明(无涉及到圆角以外的区域)的layer,直接设置layer的 backgroundColor 和 cornerRadius 属性来绘制圆角:

  1. UIView的contents无内容可以直接通过设置cornerRadius达到效果。
  2. UILable的contents也一样,所以也可通过设置cornerRadius达到效果。不过label不能直接设置backgroundColor,因为这样设置的是contents的backgroundColor,需要设置layer. backgroundColor

前面提到过UIView通过cornerRadius就可以,但是如果特殊情况需要设置layer.masksToBounds,就不要通过cornerRadius方式了,会用到如下方式:

@implementation UIView (RounderCorner)

- (void)dlj_addRounderCornerWithRadius:(CGFloat)radius size:(CGSize)size
{
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef cxt = UIGraphicsGetCurrentContext();
    
    CGContextSetFillColorWithColor(cxt, [UIColor redColor].CGColor);
    CGContextSetStrokeColorWithColor(cxt, [UIColor redColor].CGColor);
    
    CGContextMoveToPoint(cxt, size.width, size.height-radius);
    CGContextAddArcToPoint(cxt, size.width, size.height, size.width-radius, size.height, radius);//右下角
    CGContextAddArcToPoint(cxt, 0, size.height, 0, size.height-radius, radius);//左下角
    CGContextAddArcToPoint(cxt, 0, 0, radius, 0, radius);//左上角
    CGContextAddArcToPoint(cxt, size.width, 0, size.width, radius, radius);//右上角
    CGContextClosePath(cxt);
    CGContextDrawPath(cxt, kCGPathFillStroke);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
    [imageView setImage:image];
    [self insertSubview:imageView atIndex:0];
}

这个方法返回的是 UIImage,也就是说我们利用 Core Graphics 自己画出了一个圆角矩形。除了一些必要的代码外,最核心的就是 CGContextAddArcToPoint 函数。它中间的四个参数表示曲线的起点和终点坐标,最后一个参数表示半径。调用了四次函数后,就可以画出圆角矩形。最后再从当前的绘图上下文中获取图片并返回。
有了这个图片后,我们创建一个 UIImageView 并插入到视图层级的底部。
使用时,你只需要这样写:

[view dlj_addRounderCornerWithRadius:10 size:CGSizeMake(60, 30)];

我这里只是单纯为了实现圆角,当然大家在用的时候可以添加背景颜色、以及设置边框的属性。

2.ImageView添加圆角

相比于上面一种实现方法,为 UIImageView 添加圆角更为常用。它的实现思路是直接截取图片:

@implementation UIImage (ImageRoundedCorner)

- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{
    CGRect rect = CGRectMake(0, 0, size.width, size.height);
    
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
    CGContextAddPath(ctx,path.CGPath);
    CGContextClip(ctx);
    [self drawInRect:rect];
    CGContextDrawPath(ctx, kCGPathFillStroke);
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

圆角路径直接用贝塞尔曲线绘制,一个意外的 bonus 是还可以选择哪几个角有圆角效果。这个函数的效果是将原来的 UIImage 剪裁出圆角。配合着这函数,我们可以为 UIImageView 拓展一个设置圆角的方法来更加方便的使用。

提醒

  • 无论使用上面哪种方法,你都需要小心使用背景颜色。因为此时我们没有设置 masksToBounds,因此超出圆角的部分依然会被显示。因此,你不应该再使用背景颜色,可以在绘制圆角矩形时设置填充颜色来达到类似效果。
  • 在为 UIImageView 添加圆角时,请确保 image 属性不是 nil,否则这个设置将会无效。

四、扩展:其他会导致离屏渲染的解决方案

以下离屏渲染操作,按对性能影响等级从高到低进行排序:

1. shadows(阴影)

方案:在设置完layer的shadow属性之后,设置layer.shadowPath = [UIBezierPath pathWithCGRect:view.bounds].CGPath;

2.圆角(前边已解决过)

3.mask遮罩

方案:不用mask(哈哈)

4. allowsGroupOpacity(组不透明)

开启CALayer的 allowsGroupOpacity 属性后,子 layer 在视觉上的透明度的上限是其父 layer 的 opacity (对应UIView的 alpha ),并且从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。
方案:关闭 allowsGroupOpacity 属性,按产品需求自己控制layer透明度。

5. edge antialiasing(抗锯齿)

方案:不设置 allowsEdgeAntialiasing 属性为YES(默认为NO)

6. shouldRasterize(光栅化)

当视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES,此方案最为实用方便。

view.layer.shouldRasterize = true;
view.layer.rasterizationScale = view.layer.contentsScale;

但当视图内容是动态变化(如后台下载图片完毕后切换到主线程设置)时,使用此方案反而为增加系统负荷。

7.Core Graphics API(核心绘图)

Core Graphics API(核心绘图)的绘制操作会导致CPU的离屏渲染。
方案:放到后台线程中进行。

参考资料:
iOS 高效添加圆角效果实战讲解
iOS 离屏渲染优化(Offscreen Render)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,471评论 6 30
  • 转载:http://www.jianshu.com/p/32fcadd12108 每个UIView有一个伙伴称为l...
    F麦子阅读 6,170评论 0 13
  • 每个UIView有一个伙伴称为layer,一个CALayer。UIView实际上并没有把自己画到屏幕上;它绘制本身...
    shenzhenboy阅读 3,091评论 0 17
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,104评论 5 13
  • 一个无论多么强大的武林高手都会存在“死穴”,所谓的“死穴”也就是致命的弱点,有的在身体上,有的在心里上,还有的在思...
    夏野阅读 230评论 0 0