alexgophermix alexgophermix - 5 months ago 29
iOS Question

Create UIImage from UITableView with top and bottom offset

I am trying to create a utility function that will allow me to create a UIImage from a UITableView for the user to share.

The problem I'm having is that I have extraneous content both in the header view of the table (if the user is not premium it's an ad) and in the last row of the table (utility button bar for the content).

UPDATED - To this effect I have the following function:

+ (UIImage *)imageFromTable:(UITableView *)tableView
topOffset:(CGFloat)topOffset
bottomOffset:(CGFloat)bottomOffset {
UIImage *image = nil;

// store the old frame so we can reset the table after the image is created
CGRect oldFrame = tableView.frame;

// set the table frame equal to content size so content is not cut off by screen dimens
[tableView setFrame:CGRectMake(0, 0, tableView.contentSize.width, tableView.contentSize.height)];

// create a rendering frame to use for cropping our image
CGRect renderFrame = CGRectZero;
renderFrame.size.width = tableView.contentSize.width;
renderFrame.size.height = tableView.contentSize.height;
renderFrame.size.height -= bottomOffset;

// generate the image from the custome tableview frame
UIGraphicsBeginImageContextWithOptions(tableView.contentSize, tableView.opaque, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, -topOffset); // move up by top offset to crop top content
CGContextClipToRect(ctx, renderFrame); // clip to render frame to crop bottom content
[tableView.layer renderInContext:ctx];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

// TODO: remove black excess at bottom of image

// reset the table with the old frame and return the image
[tableView setFrame:oldFrame];
return image;
}


Unfortunately this method only successfully handles removal of the bottom content.

If I also do
renderFrame.size.height -= topOffset;
it will remove further height from the bottom of the table view rather than the top.


UPDATED - I now have the following image which is almost what I need, but there is a black bar left over at the bottom of the image which I cannot seem to properly remove. It's height is equal to
topOffset+bottomOffset
.

Any suggestions on how I should resolve this?

Answer

Ok so I have fairly reasonable solution. One of the major roadblocks I hit when doing this was I didn't realize that Photos centre crops images in the preview. You have to actually zoom out to see the full image... that made we waste way too much time.

UIImage from UITableView with top and bottom offset

+ (UIImage *)imageFromTable:(UITableView *)tableView
                  topOffset:(CGFloat)topOffset
               bottomOffset:(CGFloat)bottomOffset {
    UIImage *image = nil;

    // store the old frame so we can reset the table after the image is created
    CGRect oldFrame = tableView.frame;

    // set the table frame equal to content size so content is not cut off by screen dimens
    [tableView setFrame:CGRectMake(0, 0, tableView.contentSize.width, tableView.contentSize.height)];

    // create a cropping frame to use for cropping our image
    CGRect cropFrame = CGRectZero;
    cropFrame.size.width = tableView.contentSize.width;
    cropFrame.size.height = tableView.contentSize.height;
    cropFrame.size.height -= topOffset;
    cropFrame.size.height -= bottomOffset;

    // create image context with tableView content size
    // 0.0 uses [UIMain screen].scale
    UIGraphicsBeginImageContextWithOptions(cropFrame.size, tableView.opaque, 0.0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(ctx, 0, -topOffset); // move up by top offset to crop top content
    [tableView.layer renderInContext:ctx]; // render our layer
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // reset the table with the old frame and return the image
    [tableView setFrame:oldFrame];
    return image;
}

Also I need similar functionality for creating a UIImage from a UIView with similar top and bottom offset so I've added that function here as well in case you need it.

UIImage from UIView with top and bottom offset

+ (UIImage *)imageFromView:(UIView *)view
                 topOffset:(CGFloat)topOffset
              bottomOffset:(CGFloat)bottomOffset {
    UIImage *image = nil;

    // create a rendering frame to use for cropping our image
    CGRect cropFrame = CGRectZero;
    cropFrame.size.width = view.frame.size.width;
    cropFrame.size.height = view.frame.size.height;
    cropFrame.size.height -= topOffset;
    cropFrame.size.height -= bottomOffset;

    // create image context with tableView content size
    // 0.0 uses [UIMain screen].scale
    UIGraphicsBeginImageContextWithOptions(cropFrame.size, view.opaque, 0.0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(ctx, 0, -topOffset); // move up by top offset to crop top content
    [view.layer renderInContext:ctx]; // render our layer
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}

You'll notice this method is much simpler because you do't have to modify the tableview frame to match the content size before taking the "screenshot". The central chunk of code can be factored out but I left it in both solutions for easy copy pasting.