renderInContext creating black background on background thread - ios

I'm drawing a view using renderInContext to create a screenshot for storage in core-data.
I'm using this 'standard' code...
UIGraphicsBeginImageContextWithOptions( myview.bounds.size, YES, 0 );
CGContextRef ctx = UIGraphicsGetCurrentContext();
[myview.layer renderInContext:ctx];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Originally, I was doing this on the main thread which worked fine. However, as the view and its subviews became more complex (possibly hundreds of subviews with their own draw routines) the UI became too slow. So, I moved the rendering into a background thread.
This works except for the background color of the view 'myview' is black, which isn't what I've set it to...white.
With experimentation, I've noticed that if I pause my background thread for a second or two, the rendering is complete, with my background the required color. This is kind-of-ok but as the view becomes more complex, the pause needs to be longer in order to get the correct image of the view and it's not really correct to have a time delay in which I need to 'up' as the view gets more complex.
Has anyone got any suggestions how to resolve?

For info purposes, I've managed to fix this.
When I needed to render in the background, I was recreating my view and its subviews programmatically but the main view was not immediately calling drawRect when I called setNeedsDisplay...(of course!). So my background thread sometimes ran before the main view had rendered.
By forcing the view to render itself (with the code below) immediately, I could 'synchronise' everything and get the correct thumbnails.
CALayer *layer = self.layer;
[layer setNeedsDisplay];
[layer displayIfNeeded];
Hope this helps anyone else.

Alternative:
UIGraphicsBeginImageContextWithOptions(myview.bounds.size, YES, 0);
[myview drawViewHierarchyInRect:_captureView.bounds afterScreenUpdates:YES];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Related

Render a layer on top of a graphics context

In core Graphics, I am hoping to be able to draw updates to a UIView without having to redraw the entire image each time. The initial image is drawn from a CGImageRef:
CGImageRef image = CGBitmapContextCreateImage(ctxImage);
CGContextDrawImage(context, _screenRect, image);
Initially I was adding the new sections to an off screen context with the 'full image' and then creating a new CGImageRef from it before re-drawing.
CGContextDrawImage(tmp_Context,targetRect,_section);
CGImageRef image = CGBitmapContextCreateImage(tmp_Context);
CGContextDrawImage(context, _screenRect, image);
The problem with this is that in every frame update I need to redraw the entire image to the screen rather than somehow superimposing just the sections. This would yield a large performance increase if possible but not sure how to go about it. Possibly with CGLayer?
edit: I have been trying to rapidly update the view in a for loop as I receive small sections, the problem is drawRect isn't getting called to do any updates maybe because the for loop is going too fast and continually interrupting previous calls?
drawLayer = CGLayerCreateWithContext(ctxImage, targetRect.size, nil);
layerContext = CGLayerGetContext(drawLayer);
CGContextDrawImage(layerContext,targetRect,ipegSection);
CGContextDrawLayerAtPoint(currentScreen, targetRect.origin, drawLayer);
// Update the UI
[targetView setNeedsDisplay];
Can't you do (?) :
CGContextDrawImage(context, targetRect, _section);
Like this you draw only your section on the current context, not draw it on another context then redraw the screen entirely.

How to optimize memory usage in UIImage

I try to take screenshot of uiwebview and send it with observer to another UIImageView in another class.
I using this method to take screenshot:
-(UIImage*)takeScreenshoot{
#autoreleasepool {
UIGraphicsBeginImageContext(CGSizeMake(self.view.frame.size.width,self.view.frame.size.height));
CGContextRef context = UIGraphicsGetCurrentContext();
[self.webPage.layer renderInContext:context];
UIImage *__weak screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShot;
}
}
But then I have problem. Everytime I take screenshot with this method, memory rate grows about 10-15mb and it's not realising it. And if I take screenshot in every webviewdidfinishload, you can imagine how much it can take memory!
How can I fix that issue?
If possible try to use [UIScreen snapshotViewAfterScreenUpdates] which returns UIView .
This is the snapshot of currently displayed content (snapshot of app).
Even apple also says " this method is faster than trying to render the contents of the screen into a bitmap image yourself."
According to your code, you are passing this bitmap image to just display in some other UIImageView. so i think using UIScreen method is appropriate here.
To display UIWebView part only->
Create another UIView instance. Set its frame to the frame of your webView.
Now add this screenShot view as subView to createdView and set its frame such that webView portion will be displayed.
Try calling CGContextRelease(context); after you have got your screen shot.
Or as #Greg said, remove the line and use UIGraphicsGetCurrentContext() directly

iOS7's drawViewHierarchyInRect doesn't work?

From what I've read, iOS7's new drawViewHierarchyInRect is supposed to be faster than CALayer's renderInContext. And according to this and this, it should be a simple matter of calling:
[myView drawViewHierarchyInRect:myView.frame afterScreenUpdates:YES];
instead of
[myView.layer renderInContext:UIGraphicsGetCurrentContext()];
However, when I try this, I just get blank images. Full code that does the capture, where "self" is a subclass of UIView,
// YES = opaque. Ignores alpha channel, so less memory is used.
// This method for some reasons renders the
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, self.window.screen.scale); // Still slow.
if ( [AIMAppDelegate isOniOS7OrNewer] )
[self drawViewHierarchyInRect:self.frame afterScreenUpdates:YES]; // Doesn't work!
else
[self.layer renderInContext:UIGraphicsGetCurrentContext()]; // Works!
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
contentImageView.image = image; // this is empty if done using iOS7's way
and contentImageView is a UIImageView that is added as a subView to self during initialization.
Additionally, the drawing that I want captured in the image is contained in other sub-views that are also added to self as a sub-view during initialization (including contentImageView).
Any ideas why this is failing when using drawViewHierarchyInRect?
* Update *
I get an image if I draw a specific sub-view, such as:
[contentImageView drawViewHierarchyInRect:contentImageView.frame afterScreenUpdates:YES];
or
[self.curvesView drawViewHierarchyInRect:self.curvesView.frame afterScreenUpdates:YES];
however I need all the visible sub-views combined into one image.
Try it with self.bounds rather than self.frame—it’s possible you’re getting an image of your view rendered outside the boundaries of the image context you’ve created.

Render layer of UIView which is not in the View Hierarchy

What i want to achieve is to take an image of an UIView which has not been added as a subview, present and do stuff with the image and afterwards add the view to the view hierarchy.
I've searched and tried now for a while and believe, that it is simply not possible.
Obviously the problem is, that the view hasn't been drawn (called drawRect: i guess) if it hasn't been added as a subview.
Actually i thought renderInContext: would call drawRect/layer on its own.
It isn't even enough to add it as subview right before draw it to an imageContext because it won't be rendered immediately.
I take the screenshot with renderInContext: with the layer of the view, see my code here:
[self.view addSubView:view];
UIGraphicsBeginImageContextWithOptions(view.frame.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
[view.layer renderInContext:context];
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
So my question is, has anybody managed to render a not visible UIView and if how?
Well this is awkward.
After a mail conversation with a very kind apple dev support, we reviewed my code and we noticed that i simply set the hidden property to YES. - Just don't do that.
So it is straight forward to make a screenshot of a view which is not in the view hierarchy.
It was total my fault why it didn't work.
Try to addd UIView to hierarchy but keep it hidden.
- (void)takeScreenSnapshot {
UIView *capturedView = self.view;
UIView *hiddenView = self.hiddeniew; // hidden view which is
// a part of capturedView
hiddenView.hidden = NO;
BOOL retina = [self isRetinaDisplay];
UIImage *image = [capturedView captureImageWithScale:(retina) ? 2.f : 1.f];
hiddenView.hidden = YES;
}

Taking a screenshot of a view that is currently not on screen on iOS

I'm trying to make a transition between two ViewControllers. My Transition class has a property for the destination view controller. When I try to get a screenshot of the destination's view, I use this method:
+ (UIImage *)renderImageFromView:(UIView *)view withRect:(CGRect)frame {
// Create a new context the size of the frame
UIGraphicsBeginImageContextWithOptions(frame.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Render the view
[view.layer renderInContext:context];
// Get the image from the context
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
// Cleanup the context you created
UIGraphicsEndImageContext();
return renderedImage;
}
So when I want the image, I'll do this:
UIImage *image = [UIImage renderImageFromView:destinationController.view withRect:destinationController.view.bounds];
I tried it on a blank UIViewController with an orange background color and a label. I get the orange background color, but I do not get the label that was created in IB. Is there a way to get a proper screenshot of the new view I plan on showing? Thanks!
You need to make sure the view's layer is drawn to first. Calling renderInContext on a CALayer will only recursively call additional child CALayers. You need your child UIViews to draw themselves, not just using the CALayer.
Try calling
[view drawrect:frame];
instead of
[view.layer renderInContext:context];
As long as you have an open graphics context(which you do at that point), drawrect should draw directly into that.

Resources