How to save screenshot of a view with its subviews in Swift? - ios

I want to capture a view with its subviews. I'm using this code to capture the screen:
let view = self.faceTrackerContainerView
UIGraphicsBeginImageContext(CGSizeMake(view.bounds.size.width, view.bounds.size.height))
UIGraphicsGetCurrentContext()!
self.view?.drawViewHierarchyInRect(view.frame, afterScreenUpdates: true)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
These codes capture the whole screen including a button that's in front of the view. I tried these codes but it only captures the main view excluding its subviews:
let view = self.faceTrackerContainerView
let scale = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, scale);
view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
I also tried the snapshotViewAfterScreenUpdates(true) from here but I don't know how to implement it. I have a collectionView below the view and a button on top of the view (both are not in the view). The first codes capture everything with the button, collectionView, and subviews. The second codes capture the view without the subviews. What am I missing?

This is working for me,
func screenShotMethod() {
//Create the UIImage
UIGraphicsBeginImageContext(faceTrackerContainerView.frame.size)
faceTrackerContainerView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//Save it to the camera roll
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
Ref

extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
It makes an UIImage from a UIView. Then you can use that image with UIImageView(image:) method

Why not just call snapshotView(afterScreenUpdates:)?

It isn't very elegant, but hiding views before the screenshot is taken works. You can then make them visible afterwards.
view.isHidden = true
//screenshot
view.isHidden = false

Add all subviews(that you want to include in screenshot) to one
parent view.
Draw parent view hierarchy like this:
let renderer = UIGraphicsImageRenderer(size: mapParentView.bounds.size)
let image = renderer.image { ctx in
mapParentView.drawHierarchy(in: mapParentView.bounds, afterScreenUpdates: true)
}

Related

How can i pass both x and y axis along with height and width of Screen to take screenshot programmatically in swift

I added a UIImageView on an ImageView, so i want to take screenshot of these two imageviews so that they look like one, any other recommendation is also appreciated.
I made a button that helped me take a screen shot, I have added x-axis and y axis,
func takeScreenshot(_ shouldSave: Bool = true) -> UIImage {
var screenshotImage :UIImage?
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(CGSize(width: 20, height: 104), false, scale);
guard let context = UIGraphicsGetCurrentContext() else {return screenshotImage ?? UIImage(imageLiteralResourceName: "loading")}
layer.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = screenshotImage, shouldSave {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
return screenshotImage ?? UIImage(named: "loading")!
}
I expect that the screenshot taken, takes the screenshot of the imageview. Screenshot is attached,enter image description here
Put those two image views inside a UIView, constraint them all properly. Then take a screenshot of that UIView. Follow this for screenshot:
How to take screenshot of a UIView in swift?
I believe you're trying to say
You have 2 UIImageViews with different Images and then you want to take a screenshot of those 2 UIImageViews to make 1 image.
If that's the case, the easiest way to do solve it is to wrap those 2 UIImageViews inside a UIView. Then convert that UIView into UIImage. So you can have the Screenshot of those 2 UIImageViews in one.
In case you need code to convert UIView to UIImage:
extension UIView {
func toImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
I'm not sure if this the solution to your problem.

Programmatic screenshot over current context viewcontroller [duplicate]

This question already has answers here:
Screenshot on swift programmatically
(2 answers)
Closed 3 years ago.
The viewcontroller i am taking a screenshot from is set to "over current context" therefore showing an image and info in the view controller under it.
When i however take a screenshot programatically the screen turns black where it is supposed to be transparent therefore showing the viewcontroller under it.
how can this be solved?
let bounds = UIScreen.main.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, true, 0.0)
self.view.drawHierarchy(in: bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
My code (Currently returning a black screen where id like it to be transparent showing Viewcontroller under)
try to this code it's working good :
func screenShotMethod() {
let bounds = UIScreen.main.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, true, 0.0)
// myView : change with your view
myView.drawHierarchy(in: bounds, afterScreenUpdates: false)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(img!, nil, nil, nil)
}

Why the snapshot of a view can look twice too big in some cases?

ISSUE
When using various UIView extensions to take a snapshot of a UIView, the result can appear bigger (kind of zoomed in) than it should be.
Details on the Issue
I take snapshots of 2 UIViews, that we can call viewA and viewB in what follows.
viewA:
The view is added to the view hierarchy when the snapshot is created.
viewB:
The is fully defined but has not been added yet to the view hierarchy when the snapshot is created. The snapshot is then cropped x number of times and the result is added to some UIView for display.
I have tested 3 different codes to obtain the result I am looking for: one provides the desired snapshot images but with low quality rendering; the other two provides better quality image rendering and the right result for viewA but for viewB, although the snapshot appears to have the right rect (I checked the rects), the images shown appear too big (as if they were zoomed in twice).
CODE
Extension #1: Provides the right results but with low quality image
extension UIView {
func takeSnapshot() -> UIImage? {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in: UIGraphicsGetCurrentContext()!)
let snapshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshot
}
}
Extension #2: Provides the right image quality but cropped images of viewB are displayed twice too big
extension UIView {
func snapshot(of rect: CGRect? = nil, afterScreenUpdates: Bool = true) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect ?? bounds).image { _ in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
}
Extension #3: Likewise, provides the right image quality but cropped images of viewB are displayed twice too big
extension UIView {
func takeScreenshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.main.scale)
drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if (image != nil) { return image! }
return UIImage()
}
}
Last but not least, a (simplified) code to extract cropped images from the snapshot
for i in (0...numberOfCroppedImages) {
let rect = CGRect(x: CGFloat(i) * snapshot!.size.width / CGFloat(numberOfCroppedImages), y: 0, width: snapshot!.size.width / CGFloat(numberOfCroppedImages), height:snapshot!.size.height)
// defines the container view for the fragmented snapshots
let view = UIView()
view.frame = rect
// defines the layer with the fragment of the snapshot
let layer = CALayer()
layer.frame.size = rect.size
let img = snapshot?.cgImage?.cropping(to: rect)
layer.contents = img
view.layer.addSublayer(layer)
}
Attempted actions so far
I have doubled checked the all the CGRect and I have not found any issue with them: the view rectangles as well as the snapshot sizes seem to be the one expected, yet, I keep having too large rendered images with extension #2 & #3 for viewB (viewA is properly rendered), whilst extension #1 gives images of the right sizes but with too low quality to be adequately used.
Could it be an issue with drawInHierarchy(in: afterScreenUpdates:)? or alternatively the conversion to cgImage with
let img = snapshot?.cgImage?.cropping(to: rect) ?
I'd be very thankful for any pointers!
I am posting a solution I have found that works for me:
First, a snapshot extension that renders (for my case) the expected results (both quality and size wise) whether the view is added to hierarchy or not, cropped or not:
extension UIView {
func takeSnapshot(of rect: CGRect? = nil, afterScreenUpdates: Bool = true) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect ?? bounds).image { (context) in
self.layer.render(in: context.cgContext)
}
}
}
Then the updated version of the code that takes a snapshot of a rect in a view:
let view = UIView()
view.frame = rect
let img = self.takeSnapshot(of: rect, afterScreenUpdates: true).cgImage
view.layer.contents = img
The difference here consists in taking a snapshot of a rectangle in the view instead of cropping the snapshot of the whole view.
I hope this helps.

Is it possible to capture a UIImage screenshot of a UIReplicantView?

There are a lot of ways to take a screengrab of a UIView. The de facto way to capture a UIView "screengrab" of a UIView is snapshotViewAfterScreenUpdates which returns a UIReplicantView (subclass of UIView).
Is there a way to create a UIImage from this seemingly recalcitrant class?
This is the extension we used to capture UIView into UIImageView. The solution doesn't contain any usage of UIReplicatView as you suggested. But it has been working great for us:
extension UIView {
// Note: only return the image after viewDidAppear called
func takeScreenShot() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, UIScreen.main.scale)
if let scrollView = self as? UIScrollView {
let offset = scrollView.contentOffset
UIGraphicsGetCurrentContext()?.translateBy(x: -offset.x, y: -offset.y);
}
drawHierarchy(in: bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

How do I take a screenshot of a single view object instead of the entire screen?

So I have a UIImageView with some text overlayed on the top and bottom. I want to get a screenshot so I have a new image with the text overlayed on it. This is the code I was working with but I am unable to take a screenshot of the my UIImageView object properly.
func generateImage() -> UIImage
{
// Render view to an image
UIGraphicsBeginImageContext(self.view.frame.size)
view.drawViewHierarchyInRect(self.imageView.frame,
afterScreenUpdates: true)
let memeImage: UIImage =
UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return memeImage
}
Alternatively, I have also tried the code below, though the image saved is the right size/area of the screen, it ends up with blurry version of what I took a screenshot of:
UIGraphicsBeginImageContext(self.imageView.frame.size)
let context = UIGraphicsGetCurrentContext()
imageView.layer.renderInContext(context!) // tried drawInContext but it didn't work at all
let memeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return memeImage
First, using UIGraphicsBeginImageContextWithOptions will allow you to adjust the scale factor to suit your screen size. Second, passing in the view you want to capture will ensure that you are working with the correct view/subviews.
func generateImage(currentView: UIView) -> UIImage {
UIGraphicsBeginImageContextWithOptions(currentView.frame.size, true, 0.0)
currentView.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let memeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//UIImageWriteToSavedPhotosAlbum(memeImage, nil, nil, nil)
return memeImage
}

Resources