1

I am trying to create a snapshot of a UICollectionViewCell by creating a CGBitMapContext. I am not entirely clear on how to do this or how to use the associated classes, but after a bit of research, I have written the following method which is called from inside my UICollectionViewCell subclass:

- (void)snapShotOfCell
{
    float scaleFactor = [[UIScreen mainScreen] scale];
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, self.frame.size.width * scaleFactor, self.frame.size.height * scaleFactor, 8, self.frame.size.width * scaleFactor * 4, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGImageRef image = CGBitmapContextCreateImage(context);
    UIImage *snapShot = [[UIImage alloc]initWithCGImage:image];

    UIImageView *imageView = [[UIImageView alloc]initWithFrame:self.frame];
    imageView.image = snapShot;
    imageView.opaque = YES;
    [self addSubview:imageView];

     CGImageRelease(image);
     CGContextRelease(context);
     CGColorSpaceRelease(colorSpace);
}

The result is that the image does not appear. Upon debugging, I can determine that I have a valid (non nil) context, CGImage, UIImage and UIImageView, but nothing appears onscreen. Can someone tell me what I am missing?

jac300
  • 5,182
  • 14
  • 54
  • 89

1 Answers1

1

You can add this as a category to UIView and it will be accessible for any view

- (UIImage*) snapshot
{
    UIGraphicsBeginImageContextWithOptions(self.frame.size, YES /*opaque*/, 0 /*auto scale*/);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

Then you just need to do [self addSubview:[[UIImageView alloc] initWithImage:self.snapshot]] from you cell object.

[EDIT]

Providing the need for asynchronous rendering (totally understandable) this can be achieved using dispatch queues. I think this would work:

typedef void(^ImageOutBlock)(UIImage* image);

- (void) snapshotAsync:(ImageOutBlock)block
{
    CGFloat scale = [[UIScreen mainScreen] scale];
    CALayer* layer = self.layer;
    CGRect frame = self.frame;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL, frame.size.width * scaleFactor, frame.size.height * scaleFactor, 8, frame.size.width * scaleFactor * 4, colorSpace, kCGImageAlphaPremultipliedFirst);
        UIGraphicsBeginImageContextWithOptions(frame.size, YES /*opaque*/, scale);
        [layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
        dispatch_async(dispatch_get_main_queue(), ^() {
            block(image);
        });
    });
}

[EDIT]

- (void) execute
{
    __weak typeof(self) weakSelf = self;
    [self snapshotAsync:^(UIImage* image) { 
        [weakSelf addSubview:[[UIImageView alloc] initWithImage:image]] 
    }];
}
NSProgrammer
  • 2,387
  • 1
  • 24
  • 27
  • My response presumes ARC is on – NSProgrammer Sep 10 '13 at 22:27
  • Thanks, I had that code working, but the problem was that it was taking too long to execute and while executing was causing the collection view's scrolling to be unresponsive. It was suggested that I instead "redraw each cell into your own bitmap context on a background thread." So I am trying to figure out how to begin do this. – jac300 Sep 10 '13 at 22:28
  • Updated my answer. Ultimately, whatever you do, I you need to render to the context before taking the image from the context, that's the key. – NSProgrammer Sep 10 '13 at 23:00
  • Thanks for the updated answer. I tried out this code and am calling it like so: [self snapshotAsync:^(UIImage *image) { UIImageView *imageView = [[UIImageView alloc]initWithFrame:self.frame]; imageView.image = image; imageView.opaque = YES; [self addSubview:imageView]; }]; its actually working, but a few seconds into running the program crashes with a EXC_BAD_ACCESS error – jac300 Sep 11 '13 at 13:48
  • Ok, I think I know what is happening... because I am dealing with cells in collection view, if a cell is dequeued before its snapshot is taken, but the request to take the snap shot is still in the queue then it crashes. So I'll mark your answer as correct since it would work otherwise. Thanks! – jac300 Sep 11 '13 at 15:15
  • Made an edit to my reply. Added a way to make self a weak pointer to prevent potential crashing when the view/cell is deallocated. If you still get an EXC_BAD_ACCESS I recommend running your app with Instruments to find Zombies since it's likely someplace else. – NSProgrammer Sep 11 '13 at 15:16
  • I'm still getting the crash... but it only happens when I run the method, otherwise there is no crash so I'm inclined to believe that I have to cancel the request for the snap shot once the cell is deallocated. This is a great start though. Thanks for your help. – jac300 Sep 11 '13 at 15:25