Skip to content

Instantly share code, notes, and snippets.

@Josscii
Last active September 2, 2017 01:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Josscii/018e33d90b7b543dda3f3717af89b39b to your computer and use it in GitHub Desktop.
Save Josscii/018e33d90b7b543dda3f3717af89b39b to your computer and use it in GitHub Desktop.
关于 iOS 中圆角

关于 iOS 中的圆角

之所以要讨论 iOS 中的圆角,是因为圆角在各种 app 中大量被使用,而普通设置圆角的方式又会触发离屏渲染导致 FPS 的下降。

对于 UIImageView 来说,普遍的做法是将 image 通过 Core Graphics 画出一张带圆角的图片。像头像这样尺寸相对固定的图片来说这样做是很简单的,这里 详细的探讨了这件事,但是对于大图片的展示,要多考虑的一个问题是 contentMode。

有两种思路,第一种思路是剪裁图片,让这张图片刚好为相对应 contentMode 的 imageView 呈现的图片的大小。

计算的代码在这个 gist 中可以找到:

// port of http://stackoverflow.com/a/17948778/3071224
import UIKit

extension CGSize {
    static func aspectFit(aspectRatio : CGSize, boundingSize: CGSize) -> CGSize {
        var boundingSize = boundingSize
        let mW = boundingSize.width / aspectRatio.width;
        let mH = boundingSize.height / aspectRatio.height;
        
        if( mH < mW ) {
            boundingSize.width = boundingSize.height / aspectRatio.height * aspectRatio.width;
        }
        else if( mW < mH ) {
            boundingSize.height = boundingSize.width / aspectRatio.width * aspectRatio.height;
        }
        
        return boundingSize;
    }
    
    static func aspectFill(aspectRatio :CGSize, minimumSize: CGSize) -> CGSize {
        var minimumSize = minimumSize
        let mW = minimumSize.width / aspectRatio.width;
        let mH = minimumSize.height / aspectRatio.height;
        
        if( mH > mW ) {
            minimumSize.width = minimumSize.height / aspectRatio.height * aspectRatio.width;
        }
        else if( mW > mH ) {
            minimumSize.height = minimumSize.width / aspectRatio.width * aspectRatio.height;
        }
        
        return minimumSize;
    }
}

其实一般来说,我们只关心的是 aspectFill,下面就是绘制和剪裁的代码,需要注意的是要找准 image 在画布中绘制时的位置,是上下或左右居中的。

extension UIImage {
    func clipTo(boundingSize: CGSize, radius: CGFloat) -> UIImage {
        // the size when image in imageview with contentmode of .aspectFill
        let fillSize = CGSize.aspectFill(aspectRatio: size, minimumSize: boundingSize)
        let originX = (boundingSize.width - fillSize.width)/2
        let originY = (boundingSize.height - fillSize.height)/2
        UIGraphicsBeginImageContextWithOptions(boundingSize, false, 0)
        let context = UIGraphicsGetCurrentContext()!
        context.interpolationQuality = .low
        // begin at (originX, originY), draw a fillSize image
        draw(in: CGRect(x: originX, y: originY, width: fillSize.width, height: fillSize.height))
        let output = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return output
    }
}

另外将 interpolationQuality 设置为 low,可以得到和系统 imageView 一样的裁剪分辨率。

其实这种思路反而将问题复杂了,我们其实可以直接将已经设置好 image 的 imageView 画成一张图片,然后重新赋值,就像这样:

extension UIImageView {
    func resizeImage(boundingSize: CGSize, cornerRadius: CGFloat = 0.0) {
        if image != nil {
            UIGraphicsBeginImageContextWithOptions(boundingSize, false, 0)
            let context = UIGraphicsGetCurrentContext()!
            context.interpolationQuality = .low
            // clip the cornerRadius
            let roundRect = CGRect(origin: .zero, size: boundingSize)
            let roundPath = UIBezierPath(roundedRect: roundRect, cornerRadius: cornerRadius)
            roundPath.addClip()
            layer.render(in: context)
            let output = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()
            image = output
        }
    }
}

这里我还为它添加了圆角,唯一的问题是需要在 image 设置好后在调用此方法,这个开源项目的 的思路差不多就是这样的,你可以在 这里 查看它的介绍文章。

还可以像这个 SO 答案提到的那样,建立一个专门用来绘制的 layer, 这样就不用考虑 image 是否设置了。

UIGraphicsBeginImageContextWithOptions(size, false, 0)
let context = UIGraphicsGetCurrentContext()!
context.interpolationQuality = .low

imageLayer.contentsGravity = "resizeAspectFill"
imageLayer.cornerRadius = 5
imageLayer.masksToBounds = true
imageLayer.frame.size = size
imageLayer.contents = image?.cgImage
        
imageLayer.render(in: context)
        
let output = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()

这篇文章中,作者提到在 iOS 9 以后设置 cornerRadius 和 maskToBounds 已经不会触发离屏渲染了,但是不知道依据在哪里。

经过我测试,当一个屏幕内的圆角数量不多的情况下,即使 4s 也可以滑动得很流畅,总之过早的优化是万恶之源,等到出现问题了再来看这篇文章也不迟。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment