I have a UIScrollView
decendent that implements a takeScreenshot method that looks like this:
-(void)takeScreenshot {
CGRect contextRect =
Here is code that works ...
- (IBAction) renderScrollViewToImage
{
UIImage* image = nil;
UIGraphicsBeginImageContext(_scrollView.contentSize);
{
CGPoint savedContentOffset = _scrollView.contentOffset;
CGRect savedFrame = _scrollView.frame;
_scrollView.contentOffset = CGPointZero;
_scrollView.frame = CGRectMake(0, 0, _scrollView.contentSize.width, _scrollView.contentSize.height);
[_scrollView.layer renderInContext: UIGraphicsGetCurrentContext()];
image = UIGraphicsGetImageFromCurrentImageContext();
_scrollView.contentOffset = savedContentOffset;
_scrollView.frame = savedFrame;
}
UIGraphicsEndImageContext();
if (image != nil) {
[UIImagePNGRepresentation(image) writeToFile: @"/tmp/test.png" atomically: YES];
system("open /tmp/test.png");
}
}
The last few lines simply write the image to /tmp/test.png and then opens it in Preview.app. This obviously only works on in the Simulator :-)
Complete project in the ScrollViewScreenShot Github Repository
SWIFT 3 version thanks to @gleb vodovozov:
func getImageOfScrollView()->UIImage{
var image = UIImage();
UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.main.scale)
// save initial values
let savedContentOffset = self.scrollView.contentOffset;
let savedFrame = self.scrollView.frame;
let savedBackgroundColor = self.scrollView.backgroundColor
// reset offset to top left point
self.scrollView.contentOffset = CGPoint.zero;
// set frame to content size
self.scrollView.frame = CGRect(x: 0, y: 0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height)
// remove background
self.scrollView.backgroundColor = UIColor.clear
// make temp view with scroll view content size
// a workaround for issue when image on ipad was drawn incorrectly
let tempView = UIView(frame: CGRect(x: 0, y: 0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height))
// save superview
let tempSuperView = self.scrollView.superview
// remove scrollView from old superview
self.scrollView.removeFromSuperview()
// and add to tempView
tempView.addSubview(self.scrollView)
// render view
// drawViewHierarchyInRect not working correctly
tempView.layer.render(in: UIGraphicsGetCurrentContext()!)
// and get image
image = UIGraphicsGetImageFromCurrentImageContext()!;
// and return everything back
tempView.subviews[0].removeFromSuperview()
tempSuperView?.addSubview(self.scrollView)
// restore saved settings
self.scrollView.contentOffset = savedContentOffset;
self.scrollView.frame = savedFrame;
self.scrollView.backgroundColor = savedBackgroundColor
UIGraphicsEndImageContext();
return image
}
I took this solution from @Roopesh Mittal's answer and made it safer/cleaner.
Swift 5 compatible
fileprivate extension UIScrollView {
func screenshot() -> UIImage? {
let savedContentOffset = contentOffset
let savedFrame = frame
UIGraphicsBeginImageContext(contentSize)
contentOffset = .zero
frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
contentOffset = savedContentOffset
frame = savedFrame
return image
}
}
In iOS 13 I have ran into issue that this line won't work
frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
to fix the issue, I am removing scrollview from parent and then attaching in after taking the screenshot.
Here's another way of doing it, which takes the zoom level into account. I have a scrollview with 4 different UIImageView layers in it, and I want to take a screenshot of their current state:
float theScale = 1.0f / theScrollView.zoomScale;
// The viewing rectangle in absolute coordinates
CGRect visibleArea = CGRectMake((int)(theScrollView.contentOffset.x * theScale), (int)(theScrollView.contentOffset.y * theScale),
(int)(theScrollView.bounds.size.width * theScale), (int)(theScrollView.bounds.size.height * theScale));
NSArray *layers = [NSArray arrayWithObjects:imageLayer1, imageLayer2, imageLayer3, imageLayer4, nil];
UIGraphicsBeginImageContext(visibleArea.size);
for (UIImageView *layer in layers) {
CALayer *coreLayer = layer.layer;
coreLayer.bounds = CGRectMake(layer.frame.origin.x - visibleArea.origin.x, layer.frame.origin.y - visibleArea.origin.y, layer.frame.size.width, layer.frame.size.height);
[coreLayer renderInContext:UIGraphicsGetCurrentContext()];
}
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
This takes the screenshot in absolute coordinates. That is, if you have a 2048*2048 image in the scrollview and you can see about a quarter of it, then regardless of the resolution of your screen it would take a screenshot of 512*512. If you want to take a screenshot at your screen resolution (say, 320*480) then you have to adjust the image as follows, directly after the above code:
UIGraphicsBeginImageContext(theScrollView.frame.size);
[screenshot drawInRect:CGRectMake(0, 0, theScrollView.frame.size.width, theScrollView.frame.size.height)];
UIImage *smallScreenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
SWIFT 3 version:
func snapshot() -> UIImage?
{
UIGraphicsBeginImageContext(scrollView.contentSize)
let savedContentOffset = scrollView.contentOffset
let savedFrame = scrollView.frame
scrollView.contentOffset = CGPoint.zero
scrollView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
scrollView.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
scrollView.contentOffset = savedContentOffset
scrollView.frame = savedFrame
UIGraphicsEndImageContext()
return image
}
This worked for me