43

In my application, I have created a CALayer (with a few sublayers - the CALayer is composed of shapes added as sublayers).

I am trying to create a UIImage that I will be able to upload to a server (I have the code for this). However, I can't figure out how to add the CALayer to a UIImage.

Is this possible?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Brett
  • 11,637
  • 34
  • 127
  • 213

4 Answers4

113

Sounds like you want to render your layer into a UIImage. In that case, the method below should do the trick. Just add this to your view or controller class, or create a category on CALayer.

Obj-C

- (UIImage *)imageFromLayer:(CALayer *)layer
{
  UIGraphicsBeginImageContextWithOptions(layer.frame.size, NO, 0);

  [layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();

  UIGraphicsEndImageContext();

  return outputImage;
}

Swift

func imageFromLayer(layer:CALayer) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(layer.frame.size, layer.isOpaque, 0)
    layer.render(in: UIGraphicsGetCurrentContext()!)
    let outputImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return outputImage!
}
Genki
  • 3,055
  • 2
  • 29
  • 42
Todd Yandell
  • 14,656
  • 2
  • 50
  • 37
18

Todd's answer is correct, however for retina screens there should be a little difference:

- (UIImage *)imageFromLayer:(CALayer *)layer
{

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
        UIGraphicsBeginImageContextWithOptions([layer frame].size, NO, [UIScreen mainScreen].scale);
    else
        UIGraphicsBeginImageContext([layer frame].size);

    [layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return outputImage;
}
Ömer Faruk Gül
  • 965
  • 1
  • 10
  • 22
  • 4
    Specifying `0` for `UIGraphicsBeginImageContextWithOptions()`'s `scale` arg defaults it to use `UIScreen.mainScreen.scale`. _(Per the docs, “The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.”)_  Your answer functionally offers no improvement over @Todd Yandell's. – Slipp D. Thompson Sep 16 '16 at 08:56
12

I have created a Swift extension based for this:

extension UIImage {
    class func imageWithLayer(layer: CALayer) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.opaque, 0.0)
        layer.renderInContext(UIGraphicsGetCurrentContext()!)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img
    }
}

The usage:

var gradient = CAGradientLayer()
gradient.colors = [UIColor.redColor().CGColor, UIColor.blueColor().CGColor]
gradient.frame = CGRect(x: 0, y: 0, width: 200, height: 200)

let image = UIImage.imageWithLayer(gradient)
Heberti Almeida
  • 1,440
  • 18
  • 27
6

Swift 3 version with a little bit of error checking for context.

extension UIImage {
  class func image(from layer: CALayer) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(layer.bounds.size, 
  layer.isOpaque, UIScreen.main.scale)

    defer { UIGraphicsEndImageContext() }

    // Don't proceed unless we have context
    guard let context = UIGraphicsGetCurrentContext() else {
      return nil
    }

    layer.render(in: context)
    return UIGraphicsGetImageFromCurrentImageContext()
  }
}
Valérian
  • 1,068
  • 8
  • 10
Justin Wright
  • 163
  • 2
  • 9