soleil
soleil

Reputation: 13073

snapshotViewAfterScreenUpdates creating a blank image

I'm trying to create some composite UIImage objects with this code:

someImageView.image = [ImageMaker coolImage];

ImageMaker:

- (UIImage*)coolImage {
    UIView *composite = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
    UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"coolImage"]]; //This is a valid image - can be viewed when debugger stops here
    [composite addSubview:imgView];

    UIView *snapshotView = [composite snapshotViewAfterScreenUpdates:YES];
//at this point snapshotView is just a blank image
    UIImage *img = [self imageFromView:snapshotView];
    return img;

}

- (UIImage *)imageFromView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 0.0);
    [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return img;
}

I just get back a blank black image. How can I fix?

Upvotes: 3

Views: 10245

Answers (3)

funct7
funct7

Reputation: 3591

The reason it's only rendering a black rectangle is because you're drawing the view hierarchy of the snapshot view, which is non-existent.

To make it work, you should pass composite as the parameter like so:

- (UIImage*)coolImage {
    UIView *composite = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
    UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"coolImage"]]

    [composite addSubview:imgView];

    UIImage *img = [self imageFromView:composite];

    // Uncomment if you don't want composite to have imgView as its subview
    // [imgView removeFromSuperview];

    return img;

}

Before

Result

Upvotes: 1

leftspin
leftspin

Reputation: 2488

Supplying YES for -snapshotViewAfterScreenUpdates: means it needs a trip back to the runloop to actually draw the image. If you supply NO, it will try immediately, but if your view is off screen or otherwise hasn't yet drawn to the screen, the snapshot will be empty.

To reliably get an image:

- (void)withCoolImage:(void (^)(UIImage *))block {
    UIView *composite = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)];
    UIImageView *imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"coolImage"]]; //This is a valid image - can be viewed when debugger stops here
    [composite addSubview:imgView];

    UIView *snapshotView = [composite snapshotViewAfterScreenUpdates:YES];

    // give it a chance to update the screen…
    dispatch_async(dispatch_get_main_queue(), ^
    {
        // … and now it'll be a valid snapshot in here
        if(block)
        {
            block([self imageFromView:snapshotView]);
        }
    });
}

You would use it like this:

[someObject withCoolImage:^(UIImage *image){
    [self doSomethingWithImage:image];
}];

Upvotes: 8

Benjamin Mayo
Benjamin Mayo

Reputation: 6679

The snapshotted view has to be drawn to the screen for a snapshot view to not be blank. In your case, the composite view must have a superview for drawing to work.

However, you should not be using the snapshotting API for this kind of action. It is very inefficient to create a view hierarchy for the sole purpose of creating an image. Instead, use the Core Graphics API's to setup a bitmap image context, perform drawing and get back the result using UIGraphicsGetImageFromCurrentImageContext().

Upvotes: 2

Related Questions