美文网首页性能优化iOS开发中填坑工具iOSer 干货部落
老生常谈的圆角图片优化,结合SDWebImage的解决方案。

老生常谈的圆角图片优化,结合SDWebImage的解决方案。

作者: 大灰灰iOS | 来源:发表于2016-03-06 00:36 被阅读4899次

    很早就想写一篇讲述圆角优化的博客了。。结果发现这篇文章iOS 高效添加圆角效果实战讲解写得很好:摘录文章的总结部分,当拓展阅读了:

    总结
    如果能够只用 cornerRadius 解决问题,就不用优化。
    如果必须设置 masksToBounds,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。
    UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。

    过早的优化是恶魔,这句话确实很有道理

    Premature optimization is the root of all evil --Donald Knuth

    当时我做圆角优化的场景,是一个collectionView很多头像的展示。如果单纯的用layer设置cornerRadius和masksToBounds,会产生离屏渲染,小心别让圆角成了你列表的帧数杀手,这篇文章已经写的很清楚了,就不赘述了。

    self.xxView.layer.cornerRadius = 5.0f;
    self.xxView.layer.masksToBounds = YES;
    

    Advanced Graphics and Animations for iOS Apps(session 419) 学习与延伸这篇文章也介绍了离屏渲染的一些知识,有需要的同学可以去拓展阅读一下。

    正文

    其实写到这里我已经很方了。。因为感觉好多人都写过了。。
    这边再留两篇文章:
    最早接触到圆角优化是里脊串写的一次对MKMapView的性能优化
    近来还有叶孤城___reviewcode.cn里提出的解决方案。
    你们可以先看我的文章,然后再去拓展阅读,反正上面那么多链接你们也要看一会[认真脸]。

    既然原理上面的文章都讲过很多了,我真不好意思再讲一遍了,我就来说说我怎么做的吧。
    首先给imageView一个catagory,核心方法如下:

    - (void)lhy_loadImageUrlStr:(NSString *)urlStr placeHolderImageName:(NSString *)placeHolderStr radius:(CGFloat)radius;
    

    当然也有些简便的方法方便不同场景调用:

    - (void)lhy_loadImageUrlStr:(NSString *)urlStr;
    - (void)lhy_loadImageUrlStr:(NSString *)urlStr radius:(CGFloat)radius;
    

    核心方法是这样写的:

    - (void)lhy_loadImageUrlStr:(NSString *)urlStr placeHolderImageName:(NSString *)placeHolderStr radius:(CGFloat)radius {
        //something
        //这里有针对不同需求的处理,我就没贴出来了
        //...
    
        NSURL *url;
    
        if (placeHolderStr == nil) {
            placeHolderStr = @"你通用的占位图地址";
        }
       
        //这里传CGFLOAT_MIN,就是默认以图片宽度的一半为圆角
        if (radius == CGFLOAT_MIN) {
            radius = self.frame.size.width/2.0;
        }
    
        url = [NSURL URLWithString:urlStr];
    
        if (radius != 0.0) {
            //头像需要手动缓存处理成圆角的图片
            NSString *cacheurlStr = [urlStr stringByAppendingString:@"radiusCache"];
            UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:cacheurlStr];
            if (cacheImage) {
                self.image = cacheImage;
            }
            else {
                [self sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:placeHolderStr] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                    if (!error) {
                        UIImage *radiusImage = [UIImage createRoundedRectImage:image size:self.frame.size radius:radius];
                        self.image = radiusImage;
                        [[SDImageCache sharedImageCache] storeImage:radiusImage forKey:cacheurlStr];
                        //清除原有非圆角图片缓存
                        [[SDImageCache sharedImageCache] removeImageForKey:urlStr];
                    }
                }];
            }
        }
        else {
            [self sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:placeHolderStr] completed:nil];
        }
    }
    

    圆角的绘制方法是一个imageView的catagory

    static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth,
                                     float ovalHeight)
    {
        float fw, fh;
        
        if (ovalWidth == 0 || ovalHeight == 0)
        {
            CGContextAddRect(context, rect);
            return;
        }
        
        CGContextSaveGState(context);
        CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
        CGContextScaleCTM(context, ovalWidth, ovalHeight);
        fw = CGRectGetWidth(rect) / ovalWidth;
        fh = CGRectGetHeight(rect) / ovalHeight;
        
        //根据圆角路径绘制
        CGContextMoveToPoint(context, fw, fh/2); 
        CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); 
        CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); 
        CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); 
        CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
        
        CGContextClosePath(context);
        CGContextRestoreGState(context);
    }
    
    + (id)createRoundedRectImage:(UIImage*)image size:(CGSize)size radius:(NSInteger)r
    {
        int w = size.width;
        int h = size.height;
        
        UIImage *img = image;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
        //CGContextRef context = UIGraphicsGetCurrentContext();
        CGRect rect = CGRectMake(0, 0, w, h);
        
        CGContextBeginPath(context);
        addRoundedRectToPath(context, rect, r, r);
        CGContextClosePath(context);
        CGContextClip(context);
        CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
        CGImageRef imageMasked = CGBitmapContextCreateImage(context);
        img = [UIImage imageWithCGImage:imageMasked];
        
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
        CGImageRelease(imageMasked);
        
        return img;
    }
    

    这样只要两个catagory,这个头像圆角结合SDWebImage缓存的应用场景,就可以即插即用啦。

    简书已经弃用,欢迎移步我的小专栏:
    https://xiaozhuanlan.com/dahuihuiiOS

    相关文章

      网友评论

      • Wayne_Wang:感谢楼主慷慨分享,小弟遇到了个问题是 图片加载出来旋转了90度,测试发现,是根据手机拍照的方向有关,我竖屏拍照图片会被旋转90度然后加载出来,当我横屏拍照的时候图片就是正常显示。但是我直接用sd加载的时候就不会。
        Wayne_Wang:对于这个问题我测试发现是imageView的catagory设置圆形图的问题,我用另一种drawInRect的方法去做的时候图片就不会旋转了。
      • 独木舟的木:兄弟,你那保存图片到磁盘和删除图片到磁盘的方法在最新的 SDWebImage 中过时了,应该用下面的:
        // 保存图片到磁盘
        - (void)storeImage:(nullable UIImage *)image
        forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
        // 清除原有非圆角图片缓存
        - (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
        大灰灰iOS:@独木舟的木 么么哒,等我回去看看改下
      • 650c659e28f7:你好 多谢你详细的解释
        但这句话 的if条件
        ```objc
        if (radius != 0.0)
        ```
        是不是改成
        ```objc
        if (radius > 0.0001)
        ```
        会更好些
        650c659e28f7:@大灰灰iOS CGFLOAT_MIN 和 0.001是个精度的问题 可能我表述的不清楚 我的意思是 float的值 是不是不应该用 = 去判断
        大灰灰iOS:@1ast0bject 可能我想打0.f的。。然后打成了0.0...还有0.0001不如用CGFLOAT_MIN..
      • 浮游lb:你好,使用该方式绘制,如果圆角图片数量多,会造成CUP和内存猛涨,求教是否有良好解决方案?还有,SD回调中设置裁剪后的圆角图,会造成一个现象:先显示方形图,一瞬间后再修改为圆角图,也就是肉眼能很明显看到图片是从方形跳到到圆形,求教解决方案 :smile:
        大灰灰iOS:@isLin冰 你的跳动要做一个原型的占位图,然后每次加载都要清空cell。。内存问题可以提前渔预加载,最近在做一套更好的解决方案。。。
      • 7728d3c96a71:就过来踩几脚,大灰灰6666,我是小健。 :smile:
        大灰灰iOS:@Victory_LAU :mask::mask:公司没招收新人的计划
        7728d3c96a71:@大灰灰iOS 收徒弟么:heart_eyes:打下手也行啊:heart_eyes:
        大灰灰iOS:@Victory_LAU 不666,都好久没写了
      • QuerySky:测试了一下,图片模糊有两个原因:
        1.图片没有按照控件的比例来进行圆角绘制
        2.SDWebImage获取下来的图片会根据设备适配,比实际尺寸小2/1.5倍
        这样导致图片需要的部分不对,也被拉伸了
        4fb788cc8fd1:@QuerySky 那需要在哪里加点东西?
        大灰灰iOS:@QuerySky 666,我之前就在模拟器上看了。。没走真机。。。多谢多谢。。
      • fe86d48aad87:找到原因了,是UIImage分类那两段绘制圆角的方法有问题。

        在UIImageView分类SDWebImage下载图片成功回调那儿,改用下面段方法绘制成圆角图片就可以了。
        UIImage *radiusImage = [image drawRectWithRoundedCorner:radius size:self.bounds.size]; // 得到清晰圆角图片

        UIImage分类:
        - (UIImage *)drawRectWithRoundedCorner:(CGFloat)radius size:(CGSize)sizeToFit
        {

        CGRect rect = CGRectMake(0, 0, sizeToFit.width, sizeToFit.height);
        UIGraphicsBeginImageContextWithOptions(rect.size, false, [UIScreen mainScreen].scale);

        CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)].CGPath);
        CGContextClip(UIGraphicsGetCurrentContext());

        [self drawInRect:rect];
        CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
        UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        return outputImage;
        }
        大灰灰iOS:@heyuze drawRect的效率问题。。可能会比较耗资源。。不过这里SDWeb的回调应该是异步的,关系不大
      • 0fcbeabcf3b5:你的第二个category应该是UIImage的吧?
        大灰灰iOS:@Ke_Vin 这个之前一直想搞。。还没来得及搞。。楼上有个人回复的contentScale你可以试试看
        0fcbeabcf3b5:@大灰灰iOS 绘制出来后图片太模糊了,有什么解决办法么?
        大灰灰iOS:@Ke_Vin 对滴
      • 咖啡凯:👏🏻
      • 再见远洋:有没有发现绘制出来的图片是很模糊的 可以通过设置某一个属性来解决吗??
        Raybon_lee:@再见远洋 我记得是在layer层
        fe86d48aad87:@Raybon_lee 这个属性放哪儿呢
        Raybon_lee:@再见远洋 contentScale ,试一下这个
      • 明明明Y:关于图片优化。记得有一条 好像是少用[UIImage ImageNamed:...] 而是直接给图片的URL(本地或者网络的) 用ImageName 会遍历文件.
        大灰灰iOS:@明明明Y 我内存优化(一)的文章里有写。。常用的如占位图,用ImageNamed比较好,会有缓存。
      • 再见远洋:话说没有github地址呢?

        Wayne_Wang:感谢楼主慷慨分享,小弟遇到了个问题是 图片加载出来旋转了90度,测试发现,是根据手机拍照的方向有关,我竖屏拍照图片会被旋转90度然后加载出来,当我横屏拍照的时候图片就是正常显示。但是我直接用sd加载的时候就不会。
        大灰灰iOS:@再见远洋 没特意做了。。代码都在文章里了。。

      本文标题:老生常谈的圆角图片优化,结合SDWebImage的解决方案。

      本文链接:https://www.haomeiwen.com/subject/pwbgkttx.html