How do I take a full screen Screenshot in Swift? - ios

I've found this code:
func screenShotMethod() {
//Create the UIImage
UIGraphicsBeginImageContext(view.frame.size)
view.layer.renderInContext(UIGraphicsGetCurrentContext())
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//Save it to the camera roll
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
What do I need to do to get all the other elements, like the navigation bar, into the screenshots?

Instead of just throwing the answer out there, let me explain what your current code does and how to modify it to capture the full screen.
UIGraphicsBeginImageContext(view.frame.size)
This line of code creates a new image context with the same size as view. The main thing to take away here is that the new image context is the same size as view. Unless you want to capture a low resolution (non-retina) version of your application, you should probably be using UIGraphicsBeginImageContextWithOptions instead. Then you can pass 0.0 to get the same scale factor as the devices main screen.
view.layer.renderInContext(UIGraphicsGetCurrentContext())
This line of code will render the view's layer into the current graphics context (which is the context you just created). The main thing to take away here is that only view (and its subviews) are being drawn into the image context.
let image = UIGraphicsGetImageFromCurrentImageContext()
This line of code creates an UIImage object from what has been drawn into the graphics context.
UIGraphicsEndImageContext()
This line of code ends the image context. It's clean up (you created the context and should remove it as well.
The result is an image that is the same size as view, with view and its subviews drawn into it.
If you want to draw everything into the image, then you should create an image that is the size of the screen and draw everything that is on screen into it. In practice, you are likely just talking about everything in the "key window" of your application. Since UIWindow is a subclass of UIView, it can be drawn into a image context as well.

Swift 4
/// Takes the screenshot of the screen and returns the corresponding image
///
/// - Parameter shouldSave: Boolean flag asking if the image needs to be saved to user's photo library. Default set to 'true'
/// - Returns: (Optional)image captured as a screenshot
open func takeScreenshot(_ shouldSave: Bool = true) -> UIImage? {
var screenshotImage :UIImage?
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else {return nil}
layer.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = screenshotImage, shouldSave {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
return screenshotImage
}
Updated for Swift 2
The code you provide works but doesn't allow you to capture the NavigationBar and the StatusBar in your screenshot. If you want to take a screenshot of your device that will include the NavigationBar, you have to use the following code:
func screenShotMethod() {
let layer = UIApplication.sharedApplication().keyWindow!.layer
let scale = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
layer.renderInContext(UIGraphicsGetCurrentContext()!)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
}
With this code:
The first time you launch the app and call this method, the iOS device will ask you the permission to save your image in the camera roll.
The result of this code will be a .JPG image.
The StatusBar will not appear in the final image.

Details
Xcode 9.3, Swift 4.1
Xcode 10.2 (10E125) and 11.0 (11A420a), Swift 5
Tested on iOS: 9, 10, 11, 12, 13
Solution
import UIKit
extension UIApplication {
func getKeyWindow() -> UIWindow? {
if #available(iOS 13, *) {
return windows.first { $0.isKeyWindow }
} else {
return keyWindow
}
}
func makeSnapshot() -> UIImage? { return getKeyWindow()?.layer.makeSnapshot() }
}
extension CALayer {
func makeSnapshot() -> UIImage? {
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(frame.size, false, scale)
defer { UIGraphicsEndImageContext() }
guard let context = UIGraphicsGetCurrentContext() else { return nil }
render(in: context)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
return screenshot
}
}
extension UIView {
func makeSnapshot() -> UIImage? {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(size: frame.size)
return renderer.image { _ in drawHierarchy(in: bounds, afterScreenUpdates: true) }
} else {
return layer.makeSnapshot()
}
}
}
extension UIImage {
convenience init?(snapshotOf view: UIView) {
guard let image = view.makeSnapshot(), let cgImage = image.cgImage else { return nil }
self.init(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation)
}
}
Usage
imageView.image = UIApplication.shared.makeSnapshot()
// or
imageView.image = view.makeSnapshot()
// or
imageView.image = view.layer.makeSnapshot()
// or
imageView.image = UIImage(snapshotOf: view)
Old solution
Xcode 8.2.1, swift 3
Version 1 for iOS 10x
import UIKit
extension UIApplication {
var screenShot: UIImage? {
if let layer = keyWindow?.layer {
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
if let context = UIGraphicsGetCurrentContext() {
layer.render(in: context)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshot
}
}
return nil
}
}
Version 2 for iOS 9x, 10x
If you will try to use Version 1 code in iOS 9x you will have error: CGImageCreateWithImageProvider: invalid image provider: NULL.
import UIKit
extension UIApplication {
var screenShot: UIImage? {
if let rootViewController = keyWindow?.rootViewController {
let scale = UIScreen.main.scale
let bounds = rootViewController.view.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, false, scale);
if let _ = UIGraphicsGetCurrentContext() {
rootViewController.view.drawHierarchy(in: bounds, afterScreenUpdates: true)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshot
}
}
return nil
}
}
Usage
let screenShot = UIApplication.shared.screenShot!

Swift UIImage extension:
extension UIImage {
convenience init?(view: UIView?) {
guard let view: UIView = view else { return nil }
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
guard let context: CGContext = UIGraphicsGetCurrentContext() else {
UIGraphicsEndImageContext()
return nil
}
view.layer.render(in: context)
let contextImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard
let image: UIImage = contextImage,
let pngData: Data = image.pngData()
else { return nil }
self.init(data: pngData)
}
}
Usage:
let myImage: UIImage? = UIImage(view: myView)

For ease I would add an extension in it's own file
import UIKit
public extension UIWindow {
func capture() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.frame.size, self.opaque, UIScreen.mainScreen().scale)
self.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
call the extension as follows...
let window: UIWindow! = UIApplication.sharedApplication().keyWindow
let windowImage = window.capture()
Similarly, one could extended UIView to capture an image of that....

The recommended way to create a context in iOS 10 is to use UIGraphicsImageRenderer.
extension UIView {
func capture() -> UIImage? {
var image: UIImage?
if #available(iOS 10.0, *) {
let format = UIGraphicsImageRendererFormat()
format.opaque = isOpaque
let renderer = UIGraphicsImageRenderer(size: frame.size, format: format)
image = renderer.image { context in
drawHierarchy(in: frame, afterScreenUpdates: true)
}
} else {
UIGraphicsBeginImageContextWithOptions(frame.size, isOpaque, UIScreen.main.scale)
drawHierarchy(in: frame, afterScreenUpdates: true)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return image
}
}

Works with Swift 5 and iOS 13
For anyone looking for a quick answer for a function to return a screenshot of the view as a UIImage:
func getScreenshot() -> UIImage? {
//creates new image context with same size as view
// UIGraphicsBeginImageContextWithOptions (scale=0.0) for high res capture
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, 0.0)
// renders the view's layer into the current graphics context
if let context = UIGraphicsGetCurrentContext() { view.layer.render(in: context) }
// creates UIImage from what was drawn into graphics context
let screenshot: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
// clean up newly created context and return screenshot
UIGraphicsEndImageContext()
return screenshot
}
I pieced together this answer from taking the code in the question and following David Rönnqvist's suggestions (thank you for the explanation), with some tweaks.
To include the nav bar and other extras, call this method off of the window instead of the view.
I simply needed a function to get the view's screen capture, so I hope this helps anyone looking for the same

Swift 5
Captures entire screen (minus status bar) without crashing on newer devices (like iPhone 12 Pro).
extension UIApplication {
func getScreenshot() -> UIImage? {
guard let window = keyWindow else { return nil }
let bounds = UIScreen.main.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
window.drawHierarchy(in: bounds, afterScreenUpdates: true)
guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
UIGraphicsEndImageContext()
return image
}
}
Previous solutions that use UIApplication.shared.keyWindow?.layer.render(in: context) causes a memory crash on some devices like iPhone 12 Pro.

I use this method:
func captureScreen() -> UIImage {
var window: UIWindow? = UIApplication.sharedApplication().keyWindow
window = UIApplication.sharedApplication().windows[0] as? UIWindow
UIGraphicsBeginImageContextWithOptions(window!.frame.size, window!.opaque, 0.0)
window!.layer.renderInContext(UIGraphicsGetCurrentContext())
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image;
}
It captures all but the status bar, and it doesn't ask for permission to save images in the camera roll.
Hope it helps!

Swift 3 Extension for UIWindow
public extension UIWindow {
func capture() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.frame.size, self.isOpaque, UIScreen.main.scale)
self.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}]

This is how I do it in Swift 4
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
layer.render(in: UIGraphicsGetCurrentContext()!)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
now screenshot will be type UIImage

Swift 5
If you just need a true snapshot of the screen (with keyboard and status bar) in that instant:
let snap = UIScreen.main.snapshotView(afterScreenUpdates: false)
snap is a UIView with a frame automatically set to the bounds of the screen. Adding it to the view of a view controller will now give you a UI that appears frozen:
view.addSubview(snap)
Update for Scenes
iOS apps have now adopted scenes which renders the above solution ineffective. Instead of snapshotting the main screen, you must snapshot the main window of the active scene.
If your app only has a single scene, the following will work. If, however, you have multiple scenes, then you must first find the active scene (and then its main window).
if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate,
let snap = sceneDelegate.window?.snapshotView(afterScreenUpdates: false) {
view.addSubview(snap)
}
For apps with multiple scenes: https://stackoverflow.com/a/69780721/9086770

// Full Screen Shot function. Hope this will work well in swift.
func screenShot() -> UIImage {
UIGraphicsBeginImageContext(CGSizeMake(frame.size.width, frame.size.height))
var context:CGContextRef = UIGraphicsGetCurrentContext()
self.view?.drawViewHierarchyInRect(frame, afterScreenUpdates: true)
var screenShot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return screenShot
}

This is similar, hopefully it helps someone in the future.
self.view.image() //returns UIImage
Here's a Swift 3 solution
https://gist.github.com/nitrag/b3117a4b6b8e89fdbc12b98029cf98f8

This code is the latest version - 100% works
func getScreenshoot() -> UIImage {
var window: UIWindow? = UIApplication.shared.keyWindow
window = UIApplication.shared.windows[0] as? UIWindow
UIGraphicsBeginImageContextWithOptions(window!.frame.size, window!.isOpaque, 0.0)
window!.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!;
}

view.snapshotView(afterScreenUpdates: true)

My version also capture a keyboard. Swift 4.2
extension UIApplication {
var screenshot: UIImage? {
UIGraphicsBeginImageContextWithOptions(UIScreen.main.bounds.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
for window in windows {
window.layer.render(in: context)
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

Swift 4 or above.
For extension and call by UIView that you capture.
Declaration
extension UIView {
func viewCapture() -> UIImage? {
UIGraphicsBeginImageContext(self.frame.size)
guard let cgContext = UIGraphicsGetCurrentContext() else {
print("Fail to get CGContext")
return nil
}
self.layer.render(in: cgContext)
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
print("Fail to get Image from current image context")
return nil
}
UIGraphicsEndImageContext()
return image
}
}
Usage
var m_image = UIImage()
if let tempCaptureImg = self.m_Capture_View.viewCapture() {
viewController.m_image = tempCaptureImg
}
// m_Capture_View is type of UIView

After iOS 16 you can use ImageRenderer to export bitmap image data from a SwiftUI view.
Just keep in mind, you have to call it on the main thread. I used MainActor here. However, in this example, because it is firing from the Button action which is always on the main thread the MainActor is unnecessary.
struct ContentView: View {
#State private var renderedImage = Image(systemName: "photo.artframe")
#Environment(\.displayScale) var displayScale
var body: some View {
VStack(spacing: 30) {
renderedImage
.frame(width: 300, height: 300)
.background(.gray)
Button("Render SampleView") {
let randomNumber = Int.random(in: 0...100)
let renderer = ImageRenderer(content: createSampleView(number: randomNumber))
/// The default value of scale property is 1.0
renderer.scale = displayScale
if let uiImage = renderer.uiImage {
renderedImage = Image(uiImage: uiImage)
}
}
}
}
#MainActor func createSampleView(number: Int) -> some View {
Text("Random Number: \(number)")
.font(.title)
.foregroundColor(.white)
.padding()
.background(.blue)
}
}

Since UIApplication.shared.keyWindow is deprecated since iOS 13 here is updated solution for Swift 5 Xcode 14 based on #Imanou Petit's answer
extension UIWindow {
static let keyWindow: UIWindow? = {
return UIApplication.shared.delegate?.window ?? nil
}()
#discardableResult
static func takeScreenshot(shouldSave: Bool) -> UIImage? {
var screenshotImage: UIImage?
guard let layer = UIWindow.keyWindow?.layer else {
return nil
}
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
layer.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = screenshotImage, shouldSave {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
return screenshotImage
}
}
Usage:
let myScreenshotImage = UIWindow.takeScreenshot(shouldSave: false)

Try this function() its Works:
extension View {
func snapshot() -> UIImage {
let size = CGSizeMake(UIScreen.main.bounds.width,UIScreen.main.bounds.height)
let controller = UIHostingController(rootView: self.ignoresSafeArea())
let view = controller.view
view?.bounds = CGRect(origin: .zero, size: size)
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { _ in
view?.drawHierarchy(in:CGRect(origin: .zero, size: size), afterScreenUpdates: true)
}
}
}

Related

Weird error when trying to take a snapshot of a uiview using UIGraphicsImageRenderer

I'm getting the following error when I call this extension:
extension UIView {
/// Create image snapshot of view.
func snapshot(of rect: CGRect? = nil) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect ?? bounds).image { _ in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
}
The error:
vImageConvert_AnyToAny - failed width = 0 height = 1 dst component =
16bit float dstLayout = ByteOrder16Little dstBytesPerRow = 0 src
component = 16bit integer srcLayout = ByteOrder16Little srcBytesPerRow
= 0
Any ideas why this would be the case? Wondering if a color space issue... works fine on simulator, breaks on physical device.
Found a solution for others that need something similar:
extension UIView {
/// Create image snapshot of view.
func snapshot(of rect: CGRect? = nil) -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: rect!)
return renderer.image { (context) in
self.layer.render(in: context.cgContext)
}
}
}

snapshotView(afterScreenUpdates:true) returns empty view when run on real device

When running in simulator (iPhone 7 and iPhone XR) snapshotView(afterScreenUpdates: true) works great and as expected. However when I test it on my physical iPhone 7 device it returns a blank view but with the correct frame
I need the UIView object and cannot use a UIImage as many of the previous answers to similar questions suggest.
let snappedView = view.snapshotView(afterScreenUpdates: true)
Maybe this extension will work for you:
public extension UIView {
public func snapshotImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: false)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImage
}
public func snapshotView() -> UIView? {
if let snapshotImage = snapshotImage() {
return UIImageView(image: snapshotImage)
} else {
return nil
}
}
}
You may use this function to take a screenshot of your iPhone screen, if that's what you need:
func takeSnapshot() {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}

Take a screenshot with background image - iOS

I want to take a screenshot. In this screenshot, I want the text and image in it. However, I am having an issue because when I take a screenshot, I only see the text but not the image.
I think the problem is that clearContainerView only contains the text but not the image. I can't put the image inside of clearContainerView because I want the image to stretch the entire screen... and I want the text centered between the title and tab bar (as shown with green square above).
My code and pictures are below:
This is my current layout in Storyboard:
This is what I want a screenshot of:
This is the screenshot that I get:
This is my code:
#IBOutlet weak var clearContainerView: UIView!
#IBAction func takeScreenshotTapped(_ sender: UIButton) {
let screenshot = clearContainerView.screenshot()
print(screenshot)
}
extension UIView {
func screenshot() -> UIImage {
let image = UIGraphicsImageRenderer(size: bounds.size).image { _ in
drawHierarchy(in: CGRect(origin: .zero, size: bounds.size), afterScreenUpdates: true)
}
return image
}
}
Any suggestions on how to do this?
You can use the following method on your controller view to get the portion of clearContainerView which will be a snapshot view. Then you can use that view object and take a screenshot of it.
resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:
You have to pass the rect which will is your clearContainerView frame. You can pass zero insets in case you don't want any stretchable content. It return a view object which will contain your imageView portion + your complete clearContainerView. Then you can use the returned view and take its screen shot.
I tried with the following.
My original view.
The screenshot
Use this extension.
//USAGE
let image = self.view.snapshot(of:self.<your view>.frame)
Here "your view" should be the base view from the hierarchy or your can simply use
let image = self.view.snapshot(of:self.view.frame)
Extension
// UIView screenshot
extension UIView {
/// Create snapshot
///
/// - parameter rect: The `CGRect` of the portion of the view to return. If `nil` (or omitted),
/// return snapshot of the whole view.
///
/// - returns: Returns `UIImage` of the specified portion of the view.
func snapshot(of rect: CGRect? = nil) -> UIImage? {
// snapshot entire view
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
drawHierarchy(in: bounds, afterScreenUpdates: true)
let wholeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// if no `rect` provided, return image of whole view
guard let image = wholeImage, let rect = rect else { return wholeImage }
// otherwise, grab specified `rect` of image
let scale = image.scale
let scaledRect = CGRect(x: rect.origin.x * scale, y: rect.origin.y * scale, width: rect.size.width * scale, height: rect.size.height * scale)
guard let cgImage = image.cgImage?.cropping(to: scaledRect) else { return nil }
return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
}
}
Main Code:
let frame = containerView.frame
let x: CGFloat = 0
let y = frame.minY.pointsToPixel()
let width = frame.width.pointsToPixel()
let height = frame.height.pointsToPixel()
let rect = CGRect(x: x, y: y, width: width, height: height)
let image = cropImage(image: view.screenshot(), toRect: rect)
extension UIView {
func screenshot() -> UIImage {
let image = UIGraphicsImageRenderer(size: bounds.size).image { _ in
drawHierarchy(in: CGRect(origin: .zero, size: bounds.size), afterScreenUpdates: true)
}
return image
}
}
public extension CGFloat {
func pointsToPixel() -> CGFloat {
return self * UIScreen.main.scale
}
}
output:
After Screenshot:
What I've done: take a screenshot of the whole view with your method and then crop the image by converting CGPoints to pixels.
You can use the code given below to capture the screenshot. It will capture the whole window not the particular view. But take care of one thing that if you don't want your "Title", "Button" and "Tab Bar" in the screenshot then you need to hide them before the UIGraphicsBeginImageContextWithOptions and show them again after UIGraphicsEndImageContext.
func takeScreenshot() -> UIImage? {
var screenshotImage: UIImage?
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
// Hide your title label, button and tab bar here
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else {return nil}
layer.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// Unhide your title label, button and tab bar here
return screenshotImage
}
#IBAction func takeScreenshotTapped(_ sender: UIButton) {
let screenshot = takeScreenshot()
print(screenshot)
}
Everything is fine. I have made a demo for your expected output.
You just need to change your view hierarchy like this:
As pr you say the image if outside of clearContainerView
Code
class VC: UIViewController {
#IBOutlet weak var mainVw: UIView!
#IBOutlet weak var clearContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func takeImage(_ sender: Any) {
if let VC_1 = self.storyboard?.instantiateViewController(withIdentifier: "VC1") as? VC1{
VC_1.img = mainVw.screenshot()
self.present(VC_1, animated: false, completion: nil)
}
}
}
Output :
Try this!
Helper Function
struct UIGraphicsDrawImageHelper {
static func drawImage(from image: UIImageView) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(image.bounds.size, image.isOpaque, 0.0)
image.drawHierarchy(in: image.bounds, afterScreenUpdates: false)
let renderImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return renderImage
}
}
calling it
guard let image = UIGraphicsDrawImageHelper.drawImage(from: qrcodeImageView) else { return }
ImageView (Clear Container View) is the view that you want to snapshot your screen. you can change it to uiview or whatever is view.
Hope this help.

Image masking fails on iOS10 beta3

For some time now we use the following code to mask a grayscale image without transparency to a coloured image.
This always worked fine until Apple released iOS 10 beta 3. Suddenly the mask is not applied anymore resulting in just a square box being returned with te given color.
The logic behind this can be found at
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_images/dq_images.html
under the header Masking an Image with an Image Mask
The logic of this code:
* Take an grayscale image without alpha
* Create an solid image with the given color
* Create a mask from the given image
* Mask the solid image with the created mask
* Output is a masked image with also respect for colors in between (gray might be light red i.e.).
Has anyone an idea how to fix this function?
If you have XCode 8 beta 3 you can run this code and on a simulator lower than iOS 10 this will work correct and on iOS 10 it will just create a square box
Example image:
public static func image(maskedWith color: UIColor, imageNamed imageName: String) -> UIImage? {
guard let image = UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal) else {
return nil
}
guard image.size != CGSize.zero else {
return nil
}
guard
let maskRef = image.cgImage,
let colorImage = self.image(with: color, size: image.size),
let cgColorImage = colorImage.cgImage,
let dataProvider = maskRef.dataProvider
else {
return nil
}
guard
let mask = CGImage(maskWidth: maskRef.width, height: maskRef.height, bitsPerComponent: maskRef.bitsPerComponent, bitsPerPixel: maskRef.bitsPerPixel, bytesPerRow: maskRef.bytesPerRow, provider: dataProvider, decode: nil, shouldInterpolate: true),
let masked = cgColorImage.masking(mask)
else {
return nil
}
let result = UIImage(cgImage: masked, scale: UIScreen.main().scale, orientation: image.imageOrientation)
return result
}
public static func image(with color: UIColor, size: CGSize) -> UIImage? {
guard size != CGSize.zero else {
return nil
}
let rect = CGRect(origin: CGPoint.zero, size: size)
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main().scale)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setFillColor(color.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image;
}
This issue has been solved in iOS10 beta 4.

How to convert a UIView to an image

I want to convert a UIView to an image and save it in my app. Can someone please tell me how to take screenshot of a view or convert it to an image and what is the best way to save it in an app (Not camera roll)? Here is the code for the view:
var overView = UIView(frame: CGRectMake(0, 0, self.view.frame.width/1.3, self.view.frame.height/1.3))
overView.center = CGPointMake(CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds)-self.view.frame.height/16);
overView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(overView)
self.view.bringSubviewToFront(overView)
An extension on UIView should do the trick.
extension UIView {
// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
Apple discourages using UIGraphicsBeginImageContext starting iOS 10 with the introduction of the P3 color gamut. UIGraphicsBeginImageContext is sRGB and 32-bit only. They introduced the new UIGraphicsImageRenderer API that is fully color managed, block-based, has subclasses for PDFs and images, and automatically manages the context lifetime. Check out WWDC16 session 205 for more details (image rendering begins around the 11:50 mark)
To be sure that it works on every device, use #available with a fallback to earlier versions of iOS:
extension UIView {
// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
}
you can use extension
extension UIImage {
convenience init(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: image!.cgImage!)
}
}
Convert your UIView to image by drawViewHierarchyInRect:afterScreenUpdates: which is many times faster than renderInContext
Important note: do not call this function from viewDidLoad or viewWillAppear , make sure you are capturing a view after it is it displayed /loaded fully
Obj C
UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.opaque, 0.0f);
[myView drawViewHierarchyInRect:myView.bounds afterScreenUpdates:NO];
UIImage *snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
myImageView.image = snapshotImageFromMyView;
Save the edited image Photo album
UIImageWriteToSavedPhotosAlbum(snapshotImageFromMyView, nil,nil, nil);
Swift 3/4
UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.isOpaque, 0.0)
myView.drawHierarchy(in: myView.bounds, afterScreenUpdates: false)
let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
print(snapshotImageFromMyView)
myImageView.image = snapshotImageFromMyView
Super easy generalization with extension , iOS11 , swift3/4
extension UIImage{
convenience init(view: UIView) {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: (image?.cgImage)!)
}
}
Use :
//myView is completly loaded/visible , calling this code after only after viewDidAppear is call
imgVV.image = UIImage.init(view: myView)
// Simple image object
let img = UIImage.init(view: myView)
On iOS 10:
extension UIImage {
convenience init(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: (image?.cgImage)!)
}
}
Best practice as of iOS 10 and Swift 3
while still supporting iOS 9 and earlier
still works as of iOS 13, Xcode 11.1, Swift 5.1
extension UIView {
func asImage() -> UIImage? {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
guard let currentContext = UIGraphicsGetCurrentContext() else {
return nil
}
self.layer.render(in: currentContext)
return UIGraphicsGetImageFromCurrentImageContext()
}
}
}
I am unsure what the question means by:
what is the best way to save it in an app (Not camera roll)?
UIGraphicsBeginImageContext(self.view.bounds.size);
self.view.layer.renderInContext(UIGraphicsGetCurrentContext())
var screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
For example if I have a view of size: 50 50 at 100,100. I can use the following to take a screenshot:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0);
self.view.drawViewHierarchyInRect(CGRectMake(-50,-5-,view.bounds.size.width,view.bounds.size.height), afterScreenUpdates: true)
var image:UIImage = UIGraphicsGetImageFromCurrentImageContext();
In my opinion, the approach with the initialiser isn't that great because it creates two images.
I prefer this:
extension UIView {
var snapshot: UIImage? {
UIGraphicsBeginImageContext(self.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Swift 4.2, iOS 10
extension UIView {
// If Swift version is lower than 4.2,
// You should change the name. (ex. var renderedImage: UIImage?)
var image: UIImage? {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in layer.render(in: rendererContext.cgContext) }
}
}
Sample
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .blue
let view2 = UIView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
view2.backgroundColor = .red
view.addSubview(view2)
let imageView = UIImageView(image: view.image)
This works for me for Xcode 9/Swift 3.2/Swift 4 and Xcode 8/Swift 3
if #available(iOS 10.0, *) {
// for Xcode 9/Swift 3.2/Swift 4 -Paul Hudson's code
let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
let capturedImage = renderer.image {
(ctx) in
view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
}
return capturedImage
} else {
// for Xcode 8/Swift 3
UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return capturedImage!
}
Here's how to use it inside a function:
fileprivate func captureUIImageFromUIView(_ view:UIView?) -> UIImage {
guard (view != nil) else{
// if the view is nil (it's happened to me) return an alternative image
let errorImage = UIImage(named: "Error Image")
return errorImage
}
// if the view is all good then convert the image inside the view to a uiimage
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
let capturedImage = renderer.image {
(ctx) in
view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
}
return capturedImage
} else {
UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return capturedImage!
}
}
Here's how to do something with the image returned from the function:
#IBOutlet weak fileprivate var myCustomView: UIView!
var myPic: UIImage?
let myImageView = UIImageView()
#IBAction fileprivate func saveImageButtonTapped(_ sender: UIButton) {
myPic = captureUIImageFromUIView(myCustomView)
// display the pic inside a UIImageView
myImageView.image = myPic!
}
I got the Xcode 9/Swift 3.2/Swift 4 answer from Paul Hudson convert uiview to uiimage
I got the Xcode 8/Swift 3 from somewhere on SO a longgg time ago and I forgot where :(
var snapshot = overView.snapshotViewAfterScreenUpdates(false)
or in objective-c
UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];
You can use it easily by using the extension like this
// Take a snapshot from a view (just one view)
let viewSnapshot = myView.snapshot
// Take a screenshot (with every views in screen)
let screenSnapshot = UIApplication.shared.snapshot
// Take a snapshot from UIImage initialization
UIImage(view: self.view)
If you wanna use those extension method/variables, implement this
UIImage extension
extension UIImage {
convenience init(view: UIView) {
if let cgImage = view.snapshot?.cgImage {
self.init(cgImage: cgImage)
} else {
self.init()
}
}
}
UIView extension
extension UIView {
var snapshot: UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
if UIGraphicsGetCurrentContext() != nil {
drawHierarchy(in: bounds, afterScreenUpdates: true)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshot
}
return nil
}
}
UIApplication extension
extension UIApplication {
var snapshot: UIImage? {
return keyWindow?.rootViewController?.view.snapshot
}
}
or iOS 10+ you can use the new UIGraphicsImageRenderer + the recommended drawHierarchy, which in some situations can be much faster than layer.renderInContext
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
return renderer.image { _ in
self.drawHierarchy(in: CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height), afterScreenUpdates: false)
}
}
}
Thanks #Bao Tuan Diep ! I want add a supplement.
When you use the code:
yourView.layer.render(in:UIGraphicsGetCurrentContext()!)
You must notice that:
- If you had used `autoLayout` or `Masonry` in `yourView` (that you want to convert) .
- If you did not add `yourView` to another view which means that `yourView` was not used as a subview but just an object.
Then, your must use :
[yourView setNeedsLayout];
[yourView layoutIfNeeded];
to update yourView before yourView.layer.render(in:UIGraphicsGetCurrentContext()!).
Otherwise you may get an image object that contains no elements
Swift 4.2
import Foundation
import UIKit
extension UIImage {
convenience init(view: UIView) {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: (image?.cgImage)!)
}
}
using:
let img = UIImage.init(view: self.holderView)
Implementation in Swift 3 :
Add the code below , out of class scope .
extension UIImage {
convenience init(_ view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: (image?.cgImage)!)
}
}
Usage :
let image = UIImage( Your_View_Outlet )
Resorting to an extension, as some have suggested, might be okay for the common case yet not ideal in every application, depending on how one is drawing the view, i.e. as layers, or trying to display a view to its subviews.
If one is creating an image of the subview hierarchy:
let renderer = UIGraphicsImageRenderer(bounds: view.bounds)
let image = renderer.image { ctx in
self.drawHierarchy(in: self.bounds, afterScreenUpdates:
However, if your view consists of sublayers, for example CALayer, including CAShapeLayer comprised of UIBezierCurve... Something more like this may be in order:
let renderer = UIGraphicsImageRenderer(bounds: view.bounds)
let image = renderer.image { rendererContext in
for sublayer in self.layer.sublayers!.reversed() {
sublayer.render(in: rendererContext.cgContext)
}
}
And of course one might want to display a view with subviews which are layered. And since UIView.drawHierarchy() doesn't include sublayers... well, it could get more complicated
I use this for small images (because it does not work for big images):
extension UIView {
public func drawHierarchyAsImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
let image = renderer.image { _ in
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
}
return image
}
}
For high resolution images i use this:
extension UIView {
public func renderAsImage(scale: CGFloat) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = scale
let renderer = UIGraphicsImageRenderer(bounds: bounds, format: format)
return renderer.image { rendererContext in
if let presentation = layer.presentation() {
presentation.render(in: rendererContext.cgContext)
}
}
}
}
In my view hierarchy i have rotated views and thats why i use the presentation layer as mentioned here:
renderInContext does not capture rotated subviews
I implemented #Naveed J.'s method like this, and it worked like a charm.
Here was his extentsion:
extension UIView {
// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
Here is how I implemented it.
//create an image from yourView to display
//determine the frame of the view/imageimage
let screen = self.superview!.bounds
let width = screen.width / 4 //make image 1/4 width of screen
let height = width
let frame = CGRect(x: 0, y: 0, width: width, height: height)
let x = (screen.size.width - frame.size.width) * 0.5
let y = (screen.size.height - frame.size.height) * 0.5
let mainFrame = CGRect(x: x, y: y, width: frame.size.width, height: frame.size.height)
let yourView = YourView() //instantiate yourView
yourView.frame = mainFrame //give it the frame
yourView.setNeedsDisplay() //tell it to display (I am not 100% sure this is needed)
let characterViewImage = yourView.asImage()
Initializer with the new UIGraphicsImageRenderer available since iOS 10:
extension UIImage{
convenience init(view: UIView) {
let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
let canvas = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
let image = renderer.image { _ in
self.drawHierarchy(in: canvas, afterScreenUpdates: false)
}
self.init(cgImage: (image?.cgImage)!)
}
}
work fine with me !
Swift4
extension UIView {
func toImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImageFromMyView!
}
}
For view contains blurred subview (e.g. UIVisualEffectView instance), only drawViewHierarchyInRect:afterScreenUpdates works.
#ViJay Avhad's answer is correct for this case.
Using UIGraphicsImageRenderer doesn't work when the view contains Scene Kit subviews, Metal or Sprite Kit ones. In these cases, use
let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let image = renderer.image { ctx in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
please try below code.
-(UIImage *)getMainImageFromContext
{
UIGraphicsBeginImageContextWithOptions(viewBG.bounds.size, viewBG.opaque, 0.0);
[viewBG.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}

Resources