Search code examples
ioscocoa-touchuiimagecore-graphicscgcontext

Trying to create a colored circle UIImage, but it always ends up square. Why?


I have the following code. I want a blue circle created:

class func circleFromColor(_ color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) -> UIImage? {
    let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
    UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)

    guard let context = UIGraphicsGetCurrentContext() else { return nil }

    context.setFillColor(color.cgColor)
    context.fill(rect)

    let radius: CGFloat = 8.0 * UIScreen.main.scale
    let maskPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius))
    maskPath.addClip()

    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return image
}

However every single time it returns the image is a blue SQUARE. Not a circle, what gives?


Solution

  • The newer way is to use UIGraphicsImageRenderer which gives you the correct point scale automatically. Also a path can fill itself so there is no need for a clipping mask:

    func circleFromColor(_ color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) -> UIImage? {
      UIGraphicsImageRenderer(size: size).image { context in
        color.setFill()
        UIBezierPath(ovalIn: .init(origin: .zero, size: size)).fill()
      }
    }
    

    Here is how you do it the old way:

    func circleFromColor(_ color: UIColor, size: CGSize = CGSize(width: 1.0, height: 1.0)) -> UIImage? {
        let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
    
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
    
        context.setFillColor(color.cgColor)
    
        let radius: CGFloat = 8.0 * UIScreen.main.scale
        let maskPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius))
        maskPath.addClip()
        maskPath.fill()
    
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
    
        return image
    }