iOS开发(OC)swiftiOS面试-底层

iOS-裁剪圆角方法汇总

2019-04-27  本文已影响43人  路飞_Luck
目录
序言

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

一 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

本文参考
iOS离屏渲染优化(附DEMO)


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


项目链接地址 - OffscreenRenderDemo

上一篇下一篇

猜你喜欢

热点阅读