iOS

圆角总结

Posted by Renchao on February 14, 2020

圆角

圆角问题已经说了好久,总是说系统方法切圆角会有性能方面的问题,但是看了那么多零碎的文章,今天决定总结一下设置圆角的方法。

方法
  1. Layer的 cornerRadius + masksToBounds

    masksToBounds会触发离屏渲染,GPU会开辟一块新的渲染缓冲区来进行渲染,当圆角操作到达一定数量,会触发缓冲区的频繁合并和上下文频繁切换,性能上受到影响从而有掉帧卡顿的现象。

  2. 贝塞尔曲线UIBezierPath

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    imageView.image = [UIImage imageNamed:@"TestImage.jpg"];
       
    // 开始对imageView进行画图
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 0.0);
       
    // 使用贝塞尔曲线画出一个圆形图
    [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
       
    [imageView drawRect:imageView.bounds];
       
    imageView.image = UIGraphicsGetImageFromCurrentImageContext();
       
    // 结束画图
    UIGraphicsEndImageContext();
    [self.view addSubview:imageView];
    

    其中UIGraphicsBeginImageContextWithOption(CGSize size, BOOL opaque, CGFloat scale)各参数的含义:

    size:新创建的文图上下文大小

    opaque:透明开关,如果图形完全不用透明,设置为YES以优化位图的存储

    scale:缩放因子。虽然这里可以用[UIScreen mainScreen].scale来获取,但实际上设为0后,系统会自动设置正确的比例

  3. Core Graphics框架画出一个圆角

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    imageView.image = [UIImage imageNamed:@"TestImage.jpg"];
       
    // 开始对imageView进行画图
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 0.0);
       
    // 获取图形上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
       
    // 设置一个范围
    CGRect rect = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height);
       
    // 根据一个rect创建一个椭圆
    CGContextAddEllipseInRect(ctx, rect);
       
    // 裁剪
    CGContextClip(ctx);
       
    // 将原照片画到图形上下文
    [imageView.image drawInRect:rect];
       
    // 从上下文上获取裁剪后的照片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
       
    // 关闭上下文
    UIGraphicsEndImageContext();
    imageView.image = image;
    [self.view addSubview:imageView];
    
  4. CAShapeLayer和UIBezierPath设置圆角

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
    imageView.image = [UIImage imageNamed:@"TestImage.jpg"];
       
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners
    cornerRadii:imageView.bounds.size];
       
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    // 设置大小
    maskLayer.frame = imageView.bounds;
    // 设置图形样子
    maskLayer.path = maskPath.CGPath;
    imageView.layer.mask = maskLayer;
    [self.view addSubview:imageView];
    

    这种方法可以指定需要的角为圆角:

    + (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect
                              byRoundingCorners:(UIRectCorner)corners
                                    cornerRadii:(CGSize)cornerRadii;
                                       
    corners参数指定了要成为圆角的角, 枚举类型如下:
    typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
        UIRectCornerTopLeft     = 1 <&lt; 0,
        UIRectCornerTopRight    = 1 <&lt; 1,
        UIRectCornerBottomLeft  = 1 <&lt; 2,
        UIRectCornerBottomRight = 1 <&lt; 3,
        UIRectCornerAllCorners  = ~0UL
    };
    
  5. 加遮罩

    加一个圆形遮罩盖在上面,完美解决。

性能检测

Instrument 工具里的 Core Animation

方法1:

iOS 9.0之前,UIImageViewUIButton使用layer设置圆角都会触发离屏渲染。

iOS 9.0之后,UIButton会造成离屏渲染,而UIImageView的png格式不会触发离屏渲染,但如果设置其他效果如阴影等还是会触发离屏渲染。

对于文本视图实现圆角(UILabel, UIView, UITextField, UITextView),均只进行cornerRadius设置,不进行masksToBounds的设置,前三个控件没有离屏渲染,UITextView产生了离屏渲染,猜测是因为UITextView继承自UIScrollView。

方法2 方法3

无离屏渲染。

方法4

掉帧严重

总结

对于具体项目里仅一两个圆角,用layer设置圆角省时省力,目前的手机处理性能完全能够支持;对于cell里需要展示大量圆角的项目,可以采用加遮罩的方式。