Reputation: 1865
My application allow user to switch UIImageView
back and front and then user can capture that screen. Here is my code to capture the screen into UIImage
-(UIImage *)imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 2.0f);
// I even tried view.layer.presentationLayer but still not working
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
I do not use [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]
because it is slower and sometimes (when view is not visible) drawn the screenshot in black.
But problem with renderInContext is the Z position that change between UIImageView ( imageView.layer.zPosition = 0.01;etc )
. In my iPhone screen this work correctly when I assigned value to zPosition but in the capture screen it turns out wrong.
Are there anyway I can resolve this problem ? Thanks in advance
Edited: Here is what I tried to do before capture the screenshot. I use this code to make one ImageView display in front of another one.
-(void)bringOneLevelUp:(UIImageView*)imageView
{
//_imgArray is sorted by Z position order (small to big)
NSUInteger currentObjectIndex = [_imgArray indexOfObject:imageView];
if(currentObjectIndex+1< _imgArray.count){
UIImageView *upperImageView = [_imgArray objectAtIndex:currentObjectIndex+1];
CGFloat currentZIndex = imageView.layer.zPosition;
CGFloat upperZIndex= upperImageView.layer.zPosition;
imageView.layer.zPosition = upperZIndex;
upperImageView.layer.zPosition = currentZIndex;
// swap position in array
[_imgArray exchangeObjectAtIndex:currentObjectIndex withObjectAtIndex:currentObjectIndex+1];
}
}
And liked I explained earlier the result of this code in the phone screen is correct. The newImageView is in front of lastImageView. But when I captured screenshot by renderInContext. They are not.
Upvotes: 3
Views: 1700
Reputation: 17892
This is an annoying bug of Apple's, but the solution is easy, simply re-order the view you are snapshotting's subviews (and their subviews (and so on)) recursively, based on their zPositions, using a comparator.
Just call the following method, passing the view you are about to snapshot, directly before snapshotting it.
///UIGraphicsGetCurrentContext doesn't obey layer zPositions so have to adjust view heirarchy (integer index) to match zpositions (float values) for all nested subviews.
-(void)recursivelyAdjustHeirarchyOfSubviewsBasedOnZPosition:(UIView *)parentView {
NSArray *sortedSubviews = [[parentView subviews] sortedArrayUsingComparator:
^NSComparisonResult(UIView *view1, UIView *view2)
{
float zpos1 = view1.layer.zPosition;
float zpos2 = view2.layer.zPosition;
return zpos1 > zpos2;
}];
for (UIView *childView in sortedSubviews) {
[parentView bringSubviewToFront:childView];
[self recursivelyAdjustHeirarchyOfSubviewsBasedOnZPosition:childView];
}
}
Upvotes: 0
Reputation: 79
Swift 4.2
The answer of @RameshVel worked for me translating it to Swift, maybe someone needs this:
UIGraphicsBeginImageContext(drawingCanvas.frame.size)
self.renderInContext(ct: UIGraphicsGetCurrentContext()!)
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return}
UIGraphicsEndImageContext()
And in the renderInContext function:
func renderInContext(ct:CGContext){
ct.beginTransparencyLayer(auxiliaryInfo: nil)
// descendant ordered list of views based on its z-position
let orderedLayers = self.view.subviews.sorted(by: {
$0.layer.zPosition < $1.layer.zPosition
})
for l in orderedLayers {
l.layer.render(in: ct)
}
ct.endTransparencyLayer()
}
The same can be donde with CALayers if you are drawing and adding layers:
UIGraphicsBeginImageContext(drawingCanvas.frame.size)
//Canvas is a custom UIView for drawing
self.renderInContext(ct: UIGraphicsGetCurrentContext()!, canvas: canvas)
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return}
UIGraphicsEndImageContext()
renderInContext function:
func renderInContext(ct:CGContext, canvas:CustomCanvas){
ct.beginTransparencyLayer(auxiliaryInfo: nil)
let layers:[CALayer] = canvas.layer.sublayers!
let orderedLayers = layers.sorted(by: {
$0.zPosition < $1.zPosition
})
for v in orderedLayers {
v.render(in: ct)
}
ct.endTransparencyLayer()
}
Upvotes: 1
Reputation: 65877
I faced the same issue and its a bummer.
I know this question was asked about 2 years ago, so heres the answer anyway if it helps anyone incase
So changing subviews zPosition
renders perfectly fine in the app but in fact affects the renderByContext
when used to create a screenshot. I don't know why, I assume it's a bug.
So in order to get the screenshot to appear correctly as it renders in the app, we need to manually call the renderByContext
on all subviews in the correct order as specified by zPosition.
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self renderInContext:ctx];
UIImage *screenShotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
and
-(void) renderInContext:(CGContextRef)ctx {
CGContextBeginTransparencyLayer(ctx, NULL);
//self.layers is a desc ordered list of views based on its zPosition
for(UIView *view in self.layers){
[view.layer renderInContext:ctx];
}
CGContextEndTransparencyLayer(ctx);
}
Upvotes: 0
Reputation: 175
I had this issue as well. I wanted text to draw on top of an image after using renderInContext(). I solved this by positioning my views when adding them to the window rather than using the layer to set the z-position.
I used to have a text field that would set its z-position upward:
layer?.zPosition = 1
I removed this code and instead, when adding my image to the window, I used:
addSubview(image, positioned: .Below, relativeTo: nil)
This solved the problem.
Upvotes: 0