一、設置CALayer的cornerRadius
cornerRadius屬性影響layer顯示的background顏色和前景框border,對layer的contents不起作用。故一個imgView(類型為UIImageView)的image不為空,設置imgView.layer的cornerRadius,是看不出顯示圓角效果的,因為image是imgView.layer的contents部分。
這種情況下將layer的masksToBounds屬性設置為YES,可以正確的繪制出圓角效果。但是cornerRadius>0,masksToBounds=YES,會觸發GPU的離屏渲染,當一個屏幕上有多處觸發離屏渲染,會影響性能。通過勾選Instruments->Core Animation->Color Offscreen-Rendered Yellow,可以看到屏幕上觸發離屏渲染的會被渲染成黃色。離屏渲染的代價昂貴,蘋果也意識到會產生性能問題,所以iOS9以后的系統里能不產生離屏渲染的地方也就不用離屏渲染了。比如對UIImageView里png圖片設置圓角不會觸發離屏渲染。
-
對contents為空的視圖設置圓角
view.backgroundColor = [UIColor redColor]; view.layer.cornerRadius = 25; //UILabel設置backgroundColor的行為被更改,不再是設定layer的背景色而是為contents設置背景色 label.layer.backgroundColor = aColor label.layer.cornerRadius = 5
-
對contents不為空的視圖設置圓角
imageView.image = [UIImage imageNamed:@"img"]; imageView.image.layer.cornerRadius = 5; imageView.image.layer.masksToBounds = YES;
二、設置CALayer的mask
通過設置view.layer的mask屬性,可以將另一個layer蓋在view上,也可以設置圓角,但是mask同樣會觸發離屏渲染。
有兩種方式來生成遮罩,一是通過圖片生成,圖片的透明度影響著view繪制的透明度,圖片遮罩透明度為1的部分view被繪制成的透明度為0,相反圖片遮罩透明度為0的部分view被繪制成的透明度為1。二是通過貝塞爾曲線生成,view中曲線描述的形狀部分會被繪制出來。
// 通過圖片生成遮罩,
UIImage *maskImage = [UIImage imageNamed:@"someimg"];
CALayer *mask = [CALayer new];
mask.frame = CGRectMake(0, 0, maskImage.size.width, maskImage.size.height);
mask.contents = (__bridge id _Nullable)(maskImage.CGImage);
view.layer.mask = mask;
//通過貝塞爾曲線生成
CAShapeLayer *mask = [CAShapeLayer new];
mask.path = [UIBezierPath bezierPathWithOvalInRect:view.bounds].CGPath;
view.layer.mask = mask;
三、通過Core Graphics重新繪制帶圓角的視圖
通過CPU重新繪制一份帶圓角的視圖來實現圓角效果,會大大增加CPU的負擔,而且相當于多了一份視圖拷貝會增加內存開銷。但是就顯示性能而言,由于沒有觸發離屏渲染,所以能保持較高幀率。下例是繪制一個圓形圖片,繪制其它UIView并無本質區別。重新繪制的過程可以交由后臺線程來處理。
@implementation UIImage (CircleImage)
- (UIImage *)drawCircleImage {
CGFloat side = MIN(self.size.width, self.size.height);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale);
CGContextAddPath(UIGraphicsGetCurrentContext(),
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
CGContextClip(UIGraphicsGetCurrentContext());
CGFloat marginX = -(self.size.width - side) / 2.f;
CGFloat marginY = -(self.size.height - side) / 2.f;
[self drawInRect:CGRectMake(marginX, marginY, self.size.width, self.size.height)];
CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}
@end
//在需要圓角時調用如下
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *img = [[UIImage imageNamed:@"image.png"] drawCircleImage];
dispatch_async(dispatch_get_main_queue(), ^{
view.image = img;
});
});
四、通過混合圖層
此方法就是在要添加圓角的視圖上再疊加一個部分透明的視圖,只對圓角部分進行遮擋。圖層混合的透明度處理方式與mask正好相反。此方法雖然是最優解,沒有離屏渲染,沒有額外的CPU計算,但是應用范圍有限。
總結
- 在可以使用混合圖層遮擋的場景下,優先使用第四種方法。
- 即使是非iOS9以上系統,第一種方法在綜合性能上依然強于后兩者,iOS9以上由于沒有了離屏渲染更是首選。
- 方法二和方法三由于使用了貝塞爾曲線,都可以應對復雜的圓角。只不過前者犧牲幀率,后者需要大量計算和增加部分內存,需要實際情況各自取舍。
以上四種方法的Objective-C實現