iOS-裁剪圆角方法汇总(五种)

本文一共介绍 5 种方法裁剪圆角,并且分析其利弊

目录
  • cornerRadius + masksToBounds
  • 在文本视图类上实现圆角
    • UILabel
    • UITextField
    • UITextView
  • 混合图层
  • Quartz2D
  • Mask 遮罩实现
序言

在实际开发项目中,有很多场景需要裁剪圆角,如果实现方式不当,对性能会造成很大影响,本文就针对如何裁剪圆角做一个汇总。

一 cornerRadius + masksToBounds

设置圆角:

view.layer.cornerRadius = 5

文档中cornerRadius属性的说明:

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to YES causes the content to be clipped to the rounded corners.

很明了,只对前景框和背景色起作用,再看 CALayer 的结构,如果contents有内容或者内容的背景不是透明的话,还需要把这部分弄个角出来,不然合成的结果还是没有圆角,所以才要修改masksToBounds为true(在 UIView 上对应的属性是clipsToBounds,在 IB 里对应的设置是「Clip Subiews」选项)。前些日子很热闹的圆角优化文章中的2篇指出是修改masksToBounds为true而非修改cornerRadius才是触发离屏渲染的原因,但如果以「Color Offscreen-Renderd Yellow」的特征为标准的话,这两个属性单独作用时都不是引发离屏渲染的原因,他俩合体(masksToBounds = true, cornerRadius>0)才是。

  • 实例代码
// 裁剪圆角
- (void)clipRoundedCornerImage {
    //  图片
    UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    iconImgV.image = [UIImage imageNamed:@"icon_girl"];
    iconImgV.layer.cornerRadius = 100;
    iconImgV.layer.masksToBounds = YES;
    [self.view addSubview:iconImgV];
    
    [iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(iconImgV.size);
        make.top.equalTo(self.view.mas_top).offset(100);
        make.centerX.equalTo(self.view);
    }];
    
    // 视图
    UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    redView.backgroundColor = [UIColor redColor];
    redView.layer.cornerRadius = 100;
    redView.layer.masksToBounds = YES;
    [self.view addSubview:redView];
    
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(redView.size);
        make.top.equalTo(iconImgV.mas_bottom).offset(50);
        make.centerX.equalTo(self.view);
    }];
}

运行结果

image.png

总结:这种方法最简洁,代码量最少,但是性能最低,因为会触发离屏渲染。对性能要求不是很高的场合可以使用。

二 在文本视图类上实现圆角

文本视图主要是这三类:UILabel, UITextField, UITextView。其中 UITextField 类自带圆角风格的外型,UILabel 和 UITextView 要想显示圆角需要表现出与周围不同的背景色才行。想要在 UILabel 和 UITextView 上实现低成本的圆角(不触发离屏渲染),需要保证 layer 的contents呈现透明的背景色,文本视图类的 layer 的contents默认是透明的(字符就在这个透明的环境里绘制、显示),此时只需要设置 layer 的backgroundColor,再加上cornerRadius就可以搞定了。不过 UILabel 上设置backgroundColor的行为被更改了,不再是设定 layer 的背景色而是为contents设置背景色,UITextView 则没有改变这一点。

2.1 UILabel
  • 实例代码如下
- (void)drawUI {
    
    UILabel *radiusLbe = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
    radiusLbe.textColor = [UIColor blackColor];
    radiusLbe.text = @"裁剪圆角";
    radiusLbe.textAlignment = NSTextAlignmentCenter;
    // a裁剪圆角
    radiusLbe.layer.backgroundColor = [[UIColor orangeColor] CGColor];
    radiusLbe.layer.cornerRadius = 10;
    [self.view addSubview:radiusLbe];
}

效果如下

image.png

总结:UILabel设置圆角时,只需要设置cornerRadius的值即可,不会触发离屏渲染

2.2 UITextField
  • 实例代码如下
- (void)drawTextF {
    UITextField *textF = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 44)];
    textF.textColor = [UIColor blackColor];
    textF.text = @"裁剪圆角";
    textF.textAlignment = NSTextAlignmentCenter;
    // a裁剪圆角
    textF.layer.backgroundColor = [[UIColor greenColor] CGColor];
    textF.layer.cornerRadius = 10;
    [self.view addSubview:textF];
}

运行效果如下

image.png

总结:UITextField设置圆角时,只需要设置cornerRadius的值即可,不会触发离屏渲染

2.3 UITextView
  • 实例代码如下
- (void)drawTextV {
    UITextView *textV = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 20, 200)];
    textV.textColor = [UIColor whiteColor];
    textV.text = @"如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n如何在UITextView视图上实现圆角?\n";
    textV.textAlignment = NSTextAlignmentCenter;
    // a裁剪圆角
    textV.layer.backgroundColor = [[UIColor blueColor] CGColor];
    textV.layer.cornerRadius = 10;
    [self.view addSubview:textV];
}

运行效果如下:

1.gif

总结:UITextView设置圆角时,只需要设置cornerRadius的值即可,不会触发离屏渲染

三 混合图层

我们可以在需要裁剪的视图上面添加一个已经裁剪好的视图,达到显示圆角的效果。可以让美工提供一张已经是圆角的图片,但是局限性比较大,因为尺寸固定。

下面介绍如何动态生成一个任意尺寸,任意圆角的遮罩图片

3.1 裁剪成圆角
  • 实例代码如下
- (void)drawRoundedCornerImage {
    UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    iconImgV.image = [UIImage imageNamed:@"icon"];
    [self.view addSubview:iconImgV];
    
    [iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(iconImgV.size);
        make.top.equalTo(self.view.mas_top).offset(500);
        make.centerX.equalTo(self.view);
    }];
    
    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    [self.view addSubview:imgView];
    
    [imgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(imgView.size);
        make.top.equalTo(iconImgV.mas_top);
        make.leading.equalTo(iconImgV.mas_leading);
    }];
    
    // 圆形
    imgView.image = [self drawCircleRadius:100 outerSize:CGSizeMake(200, 200) fillColor:[UIColor whiteColor]];
}

// 绘制圆形
- (UIImage *)drawCircleRadius:(float)radius outerSize:(CGSize)outerSize fillColor:(UIColor *)fillColor {
    UIGraphicsBeginImageContextWithOptions(outerSize, false, [UIScreen mainScreen].scale);
    
    // 1、获取当前上下文
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    
    //2.描述路径
    // ArcCenter:中心点 radius:半径 startAngle起始角度 endAngle结束角度 clockwise:是否逆时针
    UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(outerSize.width * 0.5, outerSize.height * 0.5) radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
    [bezierPath closePath];
    
    // 3.外边
    [bezierPath moveToPoint:CGPointMake(0, 0)];
    [bezierPath addLineToPoint:CGPointMake(outerSize.width, 0)];
    [bezierPath addLineToPoint:CGPointMake(outerSize.width, outerSize.height)];
    [bezierPath addLineToPoint:CGPointMake(0, outerSize.height)];
    [bezierPath addLineToPoint:CGPointMake(0, 0)];
    [bezierPath closePath];
    
    //4.设置颜色
    [fillColor setFill];
    [bezierPath fill];
    
    CGContextDrawPath(contextRef, kCGPathStroke);
    UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return antiRoundedCornerImage;
}

效果如下

image.png

布局如下图所示


image.png

总结:这种方法是通过上面添加一层裁剪成圆形图片蒙层达到欺骗用户的效果,这种方法不会造成离屏渲染,性能较高。

3.2 裁剪任意角度

上面 3.1 是只能裁剪成圆角,扩展性不是很强,下面的方法可以裁剪成任意角度的图片,扩展性很强

  • 实例代码如下
/**
 绘制裁剪圆角后图片

 @param radius 圆角
 @param rectSize 视图尺寸
 @param fillColor 填充色
 @return 图片
 */
- (UIImage *)drawAntiRoundedCornerImageWithRadius:(float)radius rectSize:(CGSize)rectSize fillColor:(UIColor *)fillColor {
    UIGraphicsBeginImageContextWithOptions(rectSize, false, [UIScreen mainScreen].scale);
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    //2.描述路径
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    
    CGPoint hLeftUpPoint = CGPointMake(radius, 0);
    CGPoint hRightUpPoint = CGPointMake(rectSize.width - radius, 0);
    CGPoint hLeftDownPoint = CGPointMake(radius, rectSize.height);
    
    CGPoint vLeftUpPoint = CGPointMake(0, radius);
    CGPoint vRightDownPoint = CGPointMake(rectSize.width, rectSize.height - radius);
    
    CGPoint centerLeftUp = CGPointMake(radius, radius);
    CGPoint centerRightUp = CGPointMake(rectSize.width - radius, radius);
    CGPoint centerLeftDown = CGPointMake(radius, rectSize.height - radius);
    CGPoint centerRightDown = CGPointMake(rectSize.width - radius, rectSize.height - radius);
    
    [bezierPath moveToPoint:hLeftUpPoint];
    [bezierPath addLineToPoint:hRightUpPoint];
    [bezierPath addArcWithCenter:centerRightUp radius:radius startAngle:(CGFloat)(M_PI * 3 / 2) endAngle:(CGFloat)(M_PI * 2) clockwise: true];
    [bezierPath addLineToPoint:vRightDownPoint];
    [bezierPath addArcWithCenter:centerRightDown radius: radius startAngle: 0 endAngle: (CGFloat)(M_PI / 2) clockwise: true];
    [bezierPath addLineToPoint:hLeftDownPoint];
    [bezierPath addArcWithCenter:centerLeftDown radius: radius startAngle: (CGFloat)(M_PI / 2) endAngle: (CGFloat)(M_PI) clockwise: true];
    [bezierPath addLineToPoint:vLeftUpPoint];
    [bezierPath addArcWithCenter:centerLeftUp radius: radius startAngle: (CGFloat)(M_PI) endAngle: (CGFloat)(M_PI * 3 / 2) clockwise: true];
    [bezierPath addLineToPoint:hLeftUpPoint];
    [bezierPath closePath];
    
    //If draw drection of outer path is same with inner path, final result is just outer path.
    [bezierPath moveToPoint:CGPointZero];
    [bezierPath addLineToPoint:CGPointMake(0, rectSize.height)];
    [bezierPath addLineToPoint:CGPointMake(rectSize.width, rectSize.height)];
    [bezierPath addLineToPoint:CGPointMake(rectSize.width, 0)];
    [bezierPath addLineToPoint:CGPointZero];
    [bezierPath closePath];
    
    [fillColor setFill];
    [bezierPath fill];
    
    CGContextDrawPath(contextRef, kCGPathFillStroke);
    UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return antiRoundedCornerImage;
}

运行效果如下: 角度值为50,填充色分别为绿色,白色时效果

image.png

运行效果如下: 角度值为100,填充色分别为绿色,白色时效果 (圆形)

image.png

总结:这种方法扩展性很强,可以裁剪任意角度,性能很高,推荐使用。

四 Quartz2D

通过Quartz2D将图形绘制出一张圆形图片来进行显示。

  • 实例代码如下
- (UIImage *)circleImage {
    
    //1.开启图片图形上下文:注意设置透明度为非透明
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    //2.开启图形上下文
    CGContextRef ref = UIGraphicsGetCurrentContext();
    //3.绘制圆形区域(此处根据宽度来设置)
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.width);
    CGContextAddEllipseInRect(ref, rect);
    //4.裁剪绘图区域
    CGContextClip(ref);
    
    //5.绘制图片
    [self drawInRect:rect];
    
    //6.获取图片
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    //7.关闭图形上下文
    UIGraphicsEndImageContext();
    
    return image;
}

运行效果如下:

image.png

总结:这种方法性能也较高,只是局限性比较大,只能针对图片进行裁剪。我们一般可以将该方法添加到 UIImage 的分类中

@interface UIImage (CSCircleImage)

- (UIImage *)circleImage;

@end
五 Mask
5.1 使用图片

Mask 效果与混合图层的效果非常相似,只是使用同一个遮罩图像时,mask 与混合图层的效果是相反的,在 Demo 里使用反向内容的遮罩来实现圆角。实现 mask 效果使用 CALayer 的layer属性,在 iOS 8 以上可以使用 UIView 的maskView属性。

- (void)drawUI {
    UIImageView *iconImgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 80, 80)];
    iconImgV.image = [UIImage imageNamed:@"icon_girl"];
    [self.view addSubview:iconImgV];
    
    [iconImgV mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(iconImgV.size);
        make.centerY.equalTo(self.view);
        make.centerX.equalTo(self.view);
    }];
    
    // 设置Mask
    if (@available(iOS 8.0, *)) {
        iconImgV.maskView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"RoundMask"]];
    } else {
        CALayer *maskLayer = [[CALayer alloc] init];
        maskLayer.frame = iconImgV.bounds;
        maskLayer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"RoundMask"] CGImage]);
        iconImgV.layer.mask = maskLayer;
    }
}

运行效果如下:

image.png

备注:其中icon_girl.pngRoundMask分别为下面两张图片

image.png

如果所有 maskImage 相同的话,使用一个 maskImage 就够了,不然每次生成一个新的 UIImage 也会是一个性能隐患点。注意:可以使用同一个 maskImage,但不能使用同一个 maskView,不然同时只会有一个 mask 效果。

5.2 使用 CAShapeLayer 来指定混合的路径。

使用 mask 来实现圆角时也可以不用图片,而使用 CAShapeLayer 来指定混合的路径。

UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:iconImgV.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(40, 40)];
CAShapeLayer *shapLayer = [[CAShapeLayer alloc] init];
shapLayer.path = roundedRectPath.CGPath;
iconImgV.layer.mask = shapLayer;

运行效果如下:

image.png

我们通过设置cornerRadii值来设置角度值,如果为圆角,则为图片尺寸宽高的一半即可。

使用 Mask 裁剪圆角会造成离屏渲染,掉帧,卡顿。


本文参考
iOS离屏渲染优化


该文章为作者原创,如果转载,请注明出处


项目链接地址 - OffscreenRenderDemo

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

推荐阅读更多精彩内容