iOS高效添加圆角实战讲解但是很多人不知道如何设置圆角的正确方法和原理。设置圆角会带来一定的性能损失。如何提高性能是另一个需要讨论的话题。查阅了一些已有的资料,收获颇丰,但也发现了一些误导性的错误。本文总结了一些知识点,归纳如下:设置圆角的正确姿势和原则设置圆角的性能损失其他设置圆角的方法,以及最佳选择在github上克隆:CornerRadius,如果你觉得有帮助请给个star,表示支持。该项目是用Swift实现的,但相信我,即使你只知道Objective-C,你也能理解它。因为里面的重点知识和Swift无关。本文我做了一个demo,读者可以clone到我的github:CornerRadius,如果觉得有帮助,请给个star,表示支持。该项目是用Swift实现的,但相信我,即使你只知道Objective-C,你也能理解它。因为里面的重点知识和Swift无关。正确姿势首先声明一下:设置圆角非常简单,不会带来任何性能损失。因为这件事很简单,只需要一行代码:view.layer.cornerRadius=5不要急于关闭网页,也不要急于回复,让事实说话。打开Instruments,选择CoreAnimationdebugging,你会发现既没有Off-ScreenRender,也没有lowerframerate。使用Instruments分析应用可以参考我的文章:UIKit性能调优实战讲解。从截图中可以看出,第三个brownview确实设置了圆角:但是查看代码可以发现,有一个UILabel也设置了圆角,但是没有显示任何变化。为此,您可以查看cornerRadius属性的注释:默认情况下,角半径不适用于图层内容属性中的图像;它仅适用于图层的背景颜色和边框。但是,将masksToBounds属性设置为true会导致内容被剪裁成圆角。也就是说,默认情况下,该属性只影响视图的背景颜色和边框。对于内部有子视图的UILabel等控件,您无能为力。所以很多情况下我们会看到这段代码:label.layer.cornerRadius=5label.layer.masksToBounds=true我们在CustomTableViewCell的构造方法中加入第二行代码,再次运行Instrument,就可以看到圆角效果了.性能损失如果勾选ColorOffscreen-RenderedYellow,会发现标签周围有黄色标记,说明这里有离屏渲染。关于离屏渲染的介绍,还可以参考:UIKit性能调优实战讲解,本文不再赘述。需要强调的是,离屏渲染不是设置圆角造成的!通过控制变量很容易得出这个结论,因为UIView只是设置了cornerRadius,并没有出现离屏渲染。Stackoverflow、CodeReview等权威文章中提到,设置cornerRadius会造成离屏渲染,影响性能。我觉得这真是委屈了可爱的cornerRadius变量,误导了别人。虽然设置masksToBounds会造成离屏渲染,影响性能,但是这个影响有多大呢?在我的iPhone6上,即使有17个圆角视图,滑动时的帧率也在58-59fps左右波动。不过,这并不代表iOS9做了什么特别的优化,也不代表离屏渲染效果不大。主要原因是圆角不够。当我把一个UIImageView也设置成圆角的时候,也就是屏幕上有34个圆角view的时候,fps急剧下降,只有33左右。基本上已经到了影响用户体验的范围了。所以,一切不在基础上的优化都是耍流氓。如果您没有很多圆角视图并且单元格也不复杂,请不要打扰。高效设置圆角假设有很多个圆角视图(比如在UICollectionView中),如何高效地给视图添加圆角?网上的教程大多都不全面,因为这个事情需要分两种情况来考虑。普通UIView设置圆角的原理和UIImageView完全不同。有这样一种方法,试图实现cornerRadius=3的效果:overridefuncdrawRect(rect:CGRect){letmaskPath=UIBezierPath(roundedRect:rect,byRoundingCorners:.AllCorners,cornerRadii:CGSize(width:3,height:3))letmaskLayer=CAShapeLayer()maskLayer.frame=self.boundsmaskLayer.path=maskPath.CGPathself.layer.mask=maskLayer}但是这种写法太离谱了!首先,我们应该尽量避免覆盖drawRect方法。这种方法使用不当会导致内存爆炸。比如iPhone6上的UIView和屏幕一样大,即使重写一个空的drawRect方法,也至少要占用750*1134*4字节≈3.4Mb的内存。在内存鬼drawRect及其后续中,作者详细介绍了原理。根据他的测试,在和iPhone6上面的屏幕一样大的view上重写drawRect方法会消耗5.2Mb的内存。简而言之,尽可能避免重写drawRect方法。其次,这种方法本质上是用mask层mask来实现的,所以必然会导致离屏渲染。我尝试将之前的34个视图的边角圆化到这个方法,fps下降到11左右。已经属于咔出香的节奏了。忘掉这种写法吧,下面是圆角正确有效的pose。UIView添加圆角的原理是手动绘制圆角。虽然我们之前说过,直接为普通视图设置cornerRadius属性即可。但万一不可避免要使用masksToBounds,可以使用下面的方法。核心代码如下:context,startposition);//起始坐标从右边开始CGContextAddArcToPoint(context,x1,y1,x2,y2,radius);//这种代码重复四次CGContextDrawPath(UIGraphicsGetCurrentContext(),.FillStroke)letoutput=UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();returnoutput}这个方法返回的是一个UIImage,也就是说我们使用CoreGraphics自己画了一个圆角矩形。除了一些必要的代码外,最核心的就是CGContextAddArcToPoint函数。中间的四个参数代表曲线的起点和终点坐标,最后一个参数代表半径。调用该函数四次后,圆角矩形就可以画出来了。最后从当前绘图上下文中获取图片并返回。使用此图像,我们创建一个UIImageView并将其插入视图层次结构的底部:radius,borderWidth:borderWidth,backgroundColor:backgroundColor,borderColor:borderColor))self.insertSubview(imageView,atIndex:0)}}完整代码可以在项目中找到,使用时只需要这样写:letview=UIView(frame:CGRectMake(1,2,3,4))view.kt_addCorner(radius:6)给UIImageView添加圆角相对于上面的实现方式,给UIImageView添加圆角更为常见。它的实现思路是直接截取图片:extensionUIImage{funckt_drawRectWithRoundedCorner(radiusradius:CGFloat,_sizetoFit:CGSize)->UIImage{letrect=CGRect(origin:CGPoint(x:0,y:0),size:sizetoFit)UIGraphicsBeginImageContextWithOptions(rect.大小,假,UIScreen.mainScreen().scale)CGContextAddPath(UIGraphicsGetCurrentContext(),UIBezierPath(roundedRect:rect,byRoundingCorners:UIRectCorner.AllCorners,cornerRadii:CGSize(width:radius,height:radius)).CGPath)CGContextClip(UIGraphicsGetCurrentContext))self.drawInRect(rect)CGContextDrawPath(UIGraphicsGetCurrentContext(),.FillStroke)letoutput=UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();returnoutput}}圆角路径直接用贝塞尔曲线绘制,一个意想不到的好处是可以选择几种角具有圆角效果。这个函数的作用是把原来的UIImage裁剪成圆角。有了这个函数,我们可以扩展一个给UIImageView设置圆角的方法:extensionUIImageView{/**/!!!只有当imageView不为nil时,调用该方法才会有效果:param:radius圆角radius*/overridefunckt_addCorner(radiusradius:CGFloat){self.image=self.image?.kt_drawRectWithRoundedCorner(radius:radius,self.bounds.size)}}完整的代码可以在项目中找到。使用时只需要写:letimageView=letimgView1=UIImageView(image:UIImage(name:""))imageView.kt_addCorner(radius:6)背景色。因为此时我们还没有设置masksToBounds,所以超出圆角的部分还是会显示的。因此,可以不使用背景色,而是在绘制圆角矩形时设置填充色,达到类似的效果。为UIImageView添加圆角时,请确保image属性不为nil,否则此设置将无效。实测回到demo,测试刚刚定义的两种设置圆角的方法。首先取消setupContent方法中两行代码的注释:imgView1.kt_addCorner(radius:5)imgView2.kt_addCorner(radius:5)然后使用自定义方法为label和view设置圆角:view.kt_addCorner(radius:6)label.kt_addCorner(radius:6)至此,我们不仅成功添加了圆角效果,还保证了性能不会受到影响:性能测试总结如果只能用cornerRadius来解决问题,你不不需要优化它。如果一定要设置masksToBounds,可以参考圆角view的个数。如果数量少(一个页面只有几个),可以考虑不优化。UIImageView的圆角是通过直接抓图实现的,其他view的圆角可以通过CoreGraphics绘制圆角矩形实现。参考小心不要让圆角成为你列表中的帧率杀手关于性能的一些问题
