How to make a screenshot of all the content of a Scrollview? - ios

I want to create a screenshot of a UIScrollView which should contain the whole content of the scroll view, even that content, which is currently not visible to the user. For that I tried the following two methods:
func snapShot(view:UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, 0);
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true);
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
func snapShotScrollView(scrollView:UIScrollView) -> UIImage {
let bounds = scrollView.bounds;
scrollView.bounds.size = scrollView.contentSize;
let image = snapShot(scrollView);
scrollView.bounds = bounds;
return image;
}
But the resulting image is still just showing those view elements inside the scroll view which are currently visible to the user. But I want to see all views.
How can I do that?
EDIT
I also tried:
func snapshot() -> UIImage? {
var image: 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()!)
image = UIGraphicsGetImageFromCurrentImageContext();
scrollView.contentOffset = savedContentOffset;
scrollView.frame = savedFrame;
UIGraphicsEndImageContext();
return image
}
Edit 2
My UIScrollView is placed inside a UIView and does contain a UIStackView. The View is designed as a popover view so that it looks like a dialogue is popping up. The code sample from my first edit is working in a blank UIViewController with only one UIScrollView but not in the mentioned constellation.

To Swift from this answer, adding a test ViewController:
class ScreenShotTestViewController: UIViewController {
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: CGRect(origin: CGPoint.zero, size: view.frame.size))
scrollView.contentSize = CGSize(width: view.frame.size.width, height: view.frame.size.height * 2)
scrollView.backgroundColor = UIColor.yellow
view.addSubview(scrollView)
let label = UILabel(frame: CGRect(x: 0.0, y: view.frame.size.height * 1.5, width: view.frame.size.width, height: 44.0))
label.text = "Hello World!"
scrollView.addSubview(label)
let screenShot = snapshot()
}
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
}
}
Results in an image of the complete content, including subviews:

Related

Getting a full screenshot of a UIScrollView in iOS 13

I am using Swift 5 and iOS 13. I am trying to take a screenshot of the entire scrollview image and print it, but the bottom appears in white. How can i solve this problem? I showed and explained everything in the picture below.
I am also checked this topic
My extension code like this:
extension UIScrollView {
func screenshot() -> UIImage {
let savedContentOffset = contentOffset
let savedFrame = frame
UIGraphicsBeginImageContextWithOptions(contentSize, false, 0)
contentOffset = .zero
frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
contentOffset = savedContentOffset
frame = savedFrame
return image ?? UIImage()
}
}
Here is the code snippet that worked for me.
extension UIView {
func snapshot(scrollView: UIScrollView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(scrollView.contentSize, false, UIScreen.main.scale)
layer.render(in: UIGraphicsGetCurrentContext()!)
let savedContentOffset = scrollView.contentOffset
let savedFrame = frame
scrollView.contentOffset = CGPoint.zero
frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
scrollView.contentOffset = savedContentOffset
frame = savedFrame
UIGraphicsEndImageContext()
return image
}
}
And when you call it:
view.snapshot(scrollView: scrollView)
It's seems like iOS 13 bug but I found the solution in this answer.
The solution is I am removing scrollview from superview and then adding in with its old constraints after taking the screenshot. Yeah, it's stupid but it just works that way :(
scrollView.removeFromSuperview()
let print = scrollView.screenshot()
mainView.addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
make.top.equalTo(topView.snp.bottom)
make.left.bottom.right.equalToSuperview()
}

How to get a screenshot of the View (Drawing View) used for drawing using UIBezeir path

I have a Drawing View which is on a Scroll View. After drawing is completed I need a screenshot of the drawing which I will be uploading to the server.
I am using UIBezeir path to draw on the view.
let path = UIBezierPath()
for i in self.drawView.path{
path.append(i)
}
self.drawView.path is an NSArray with all the bezeir paths of the drawing.
But when I use the bounding box of this path and get max and min values of coordinates and try to capture a screenshot I get this
var rect:CGRect = CGRect(x: path.bounds.minX, y: path.bounds.minY, width: path.bounds.maxX, height: path.bounds.maxY)
I also tried to give the bounds of the path itself
let rect:CGRect = CGRect(x: path.bounds.origin.x - 5, y: path.bounds.origin.y - 5, width: path.bounds.size.width + 5, height: path.bounds.size.height + 5)
Just for reference I tried using this rect and create a view (clear color with border layer) and placed it over the Drawing, it work pretty fine but when I try to capture an image it goes out of bounds
This is the function I am using to capture the screen
func imgScreenShot(bounds:CGRect) -> UIImage{
let rect: CGRect = bounds
self.drawView.isOpaque = false
self.drawView.backgroundColor = UIColor.clear
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
var context: CGContext? = UIGraphicsGetCurrentContext()
if let aContext = context {
self.drawView.layer.render(in: aContext)
}
var capturedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//let finalImage = scaleImage(image: capturedImage)
return capturedImage!
}
I am also tried getting a UIView with this function
let vw = self.drawView.resizableSnapshotView(from: rect, afterScreenUpdates: true, withCapInsets: UIEdgeInsets.zero)
This gives me a perfect UIView with the drawing in that, but again when I try to convert the UIView to UIImage using the function giving the views bounds, I get a blank image.
Can anyone suggest what I am doing wrong or any other solution for how I can get this, bounds of image starting right exactly at the bounds of the drawing
let vw = self.drawView.resizableSnapshotView(from: rect2, afterScreenUpdates: true, withCapInsets: UIEdgeInsets.zero)
vw?.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
vw?.layer.borderColor = UIColor.red.cgColor
vw?.layer.borderWidth = 1
self.drawView.addSubview(vw!)
let image = vw?.snapshotImage
let imgView = UIImageView(frame: CGRect(x: 250, y: 50, width: 100, height: 100))
imgView.layer.borderColor = UIColor.gray.cgColor
imgView.layer.borderWidth = 1
self.drawView.addSubview(imgView)
Make an extension of UIView and UIImage , so in whole application lifecycle you can use those methods(which one i will be describe at below) for capture the screenshort of any perticular UIView and resize the existing image(if needed).
Here is the extension of UIView :-
extension UIView {
var snapshotImage : UIImage? {
var snapShotImage:UIImage?
UIGraphicsBeginImageContext(self.frame.size)
if let context = UIGraphicsGetCurrentContext() {
self.layer.render(in: context)
if let image = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
snapShotImage = image
}
}
return snapShotImage
}
}
Here is the extension of UIImage :-
extension UIImage {
func resizeImage(newSize:CGSize) -> UIImage? {
var newImage:UIImage?
let horizontalRatio = newSize.width / size.width
let verticalRatio = newSize.height / size.height
let ratio = max(horizontalRatio, verticalRatio)
let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
UIGraphicsBeginImageContext(newSize)
if let _ = UIGraphicsGetCurrentContext() {
draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: newSize))
if let image = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
newImage = image
}
}
return newImage
}
}
How to use those functions in our desired class ?
if let snapImage = yourUIView.snapshotImage {
///... snapImage is the desired image you want and its dataType is `UIImage`.
///... Now resize the snapImage into desired size by using this one
if let resizableImage = snapImage.resizeImage(newSize: CGSize(width: 150.0, height: 150.0)) {
print(resizableImage)
}
}
here yourUIView means , the one you have taken for drawing some inputs. it can be IBOutlet as well as your UIView (which you have taken programmatically)

iOS - Scrollview screenshot is showing grey parts

I have the following code:
func takeScrollScreenshot() -> UIImage? {
let scrollview: UIScrollView = self as! UIScrollView
UIGraphicsBeginImageContext(scrollview.contentSize)
let savedContentOffset = scrollview.contentOffset
let savedFrame = scrollview.frame
scrollview.contentOffset = CGPoint.zero;
scrollview.frame = CGRect(x: CGFloat(0),
y: CGFloat(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
}
The hierarchy is like this: View -> ScrollView -> View --> the remaining views
The scrollview screenshot, the problem is why the bottom part is not colored.
Screenshot of the inner view.
How should looks like
I found the solution.
I just need add the line drawHierarchy(in: self.bounds, afterScreenUpdates: true)
func takeScrollScreenshot() -> UIImage? {
let scrollview: UIScrollView = self as! UIScrollView
UIGraphicsBeginImageContext(scrollview.contentSize)
let savedContentOffset = scrollview.contentOffset
let savedFrame = scrollview.frame
scrollview.contentOffset = CGPoint.zero;
scrollview.frame = CGRect(x: CGFloat(0),
y: CGFloat(0),
width: scrollview.contentSize.width,
height: scrollview.contentSize.height);
scrollview.layer.render(in: UIGraphicsGetCurrentContext()!)
//I forget add this line.
drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext();
scrollview.contentOffset = savedContentOffset
scrollview.frame = savedFrame
UIGraphicsEndImageContext()
return image
}

Content of scrollview clear when taking screenshot

I have a scrollview and i need a screenshot of the complete content of that scrollview, i search and try many solutions on stackoverflow but that provide only the screenshot of the total height of the viewcontroller.
For example if the height of the scrollview is 1096 and Viewcontroller height is 532 , it screenshot the content according to the Viewcontroller height
I found only this code useful on stackoverflow and it return the complete height of the scrollview but it clear the content of scrollview in the Viewcontroller. Any help how to retain the content of scrollview or any better solution to take a complete screenshot in swift. Thankyou
func getImageOfScrollView()->UIImage{
var image = UIImage();
UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.mainScreen().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 = CGPointZero;
// set frame to content size
self.scrollView.frame = CGRectMake(0, 0, self.scrollView.contentSize.width, self.scrollView.contentSize.height);
// remove background
self.scrollView.backgroundColor = UIColor.clearColor()
// make temp view with scroll view content size
// a workaround for issue when image on ipad was drawn incorrectly
let tempView = UIView(frame: CGRectMake(0, 0, self.scrollView.contentSize.width, 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.renderInContext(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
}
Using this as I guide, I think you can snapshot any view using:
func snapShot(view:UIView) -> UIImage
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, 0)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Though with a scroll view this will only return an image of what's visible in the scroll view's frame, so we can create a helper function:
func snapShotScrollView(scrollView:UIScrollView) -> UIImage
{
let bounds = scrollView.bounds
scrollView.bounds.size = scrollView.contentSize
let image = snapShot(scrollView)
scrollView.bounds = bounds
return image
}
This will briefly adjust our scroll view's bounds to it's content size.
I don't know how performant this code is, but this works for me in a swift playground. For example, running this code:
let scrollView = UIScrollView(frame:CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0))
scrollView.backgroundColor = UIColor.whiteColor()
scrollView.contentSize = CGSize(width: 600.0, height: 600.0)
let blueView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0))
blueView.backgroundColor = UIColor.blueColor()
let redView = UIView(frame: CGRect(x: 200.0, y: 200.0, width: 400.0, height: 600.0))
redView.backgroundColor = UIColor.redColor()
scrollView.addSubview(blueView)
scrollView.addSubview(redView)
let image = snapShotScrollView(scrollView)
Results in
where as calling snapShot by itself only returns a blue square. After the snapshot the scroll view is the original size with the original content.

UITapGestureRecognizer called only once

I have this code:
func initPlaceHolder(width: CGFloat, height: CGFloat){
var firstPlaceHolderPosition: CGFloat = 0;
UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 0)
let context = UIGraphicsGetCurrentContext()
let rectangle = CGRect(x: 0, y: 0, width: width, height: height)
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
CGContextSetLineWidth(context, 1)
CGContextAddRect(context, rectangle)
CGContextDrawPath(context, .FillStroke)
let img = UIGraphicsGetImageFromCurrentImageContext()
for i in 1...4 {
let imageView = StompUIImageView(frame: CGRect(x: firstPlaceHolderPosition, y: 0, width: width, height: height))
let tap = UITapGestureRecognizer(target: self, action: "doubleTapped:")
tap.numberOfTapsRequired = 2
imageView.image = img
imageView.addGestureRecognizer(tap)
imageView.userInteractionEnabled = true
imageView.stompID = String(i)
imageView.stompSlot = i
addSubview(imageView)
firstPlaceHolderPosition = firstPlaceHolderPosition + width + 10
UIGraphicsEndImageContext()
}
}
func doubleTapped(sender: UITapGestureRecognizer) {
let view = sender.view as! StompUIImageView
print(view.stompID)
}
Basically the doubleTapped handler is called only for the first UIImageView and not for all 4.
Sorry being new to ios development I have difficulties to understand way.
Thanks for any help
Try this ...
Replace your code with this one....
I hope this will help you.
func initPlaceHolder(width: CGFloat, height: CGFloat){
var firstPlaceHolderPosition: CGFloat = 0;
UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 0)
let context = UIGraphicsGetCurrentContext()
let rectangle = CGRect(x: 0, y: 0, width: width, height: height)
CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
CGContextSetLineWidth(context, 1)
CGContextAddRect(context, rectangle)
CGContextDrawPath(context, .FillStroke)
let img = UIGraphicsGetImageFromCurrentImageContext()
let tap = UITapGestureRecognizer(target: self, action: "doubleTapped:")
tap.numberOfTapsRequired = 2
for i in 1...4 {
let imageView = StompUIImageView(frame: CGRect(x: firstPlaceHolderPosition, y: 0, width: width, height: height))
imageView.image = img
imageView.addGestureRecognizer(tap)
imageView.userInteractionEnabled = true
imageView.stompID = String(i)
imageView.stompSlot = i
addSubview(imageView)
firstPlaceHolderPosition = firstPlaceHolderPosition + width + 10
UIGraphicsEndImageContext()
}
}
func doubleTapped(sender: UITapGestureRecognizer) {
let view = sender.view as! StompUIImageView
print(view.stompID)
}
My code was fine answering my own issue.
The problem was the line: addSubview(imageView)
I was adding 4 placeholder (about 300px) to a container smaller than the sum of the widths of all my placeholders.
Although I was seeing all the placeholders correctly displayed, the frame wasn't big enough to contain them and so the double tap not recognised.
Making the container bigger solved the issue.

Resources