Reputation: 897
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?
Upvotes: 81
Views: 99298
Reputation: 487
In my use case, I will feedback with all scenes. So,
public extension UIApplication {
@MainActor
func screenshots() -> [UIImage] {
let windows = connectedScenes.compactMap({ ($0 as? UIWindowScene)?.keyWindow })
return windows.map { window in
let renderer = UIGraphicsImageRenderer(size: window.bounds.size * window.contentScaleFactor)
return renderer.image { context in
window.layer.render(in: context.cgContext)
}
}
}
}
And I added an extension of CGSize:
public extension CGSize {
static func * (lhs: Self, rhs: CGFloat) -> Self {
return .init(width: lhs.width * rhs, height: lhs.height * rhs)
}
}
Upvotes: 1
Reputation: 241
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)
}
}
}
Upvotes: 1
Reputation: 1008
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)
Upvotes: 0
Reputation: 323
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)
}
}
Upvotes: 0
Reputation: 12405
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
Upvotes: 3
Reputation: 518
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.
Upvotes: 4
Reputation: 25304
Tested on iOS: 9, 10, 11, 12, 13
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)
}
}
imageView.image = UIApplication.shared.makeSnapshot()
// or
imageView.image = view.makeSnapshot()
// or
imageView.image = view.layer.makeSnapshot()
// or
imageView.image = UIImage(snapshotOf: view)
Xcode 8.2.1, swift 3
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
}
}
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
}
}
let screenShot = UIApplication.shared.screenShot!
Upvotes: 44
Reputation: 1793
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!;
}
Upvotes: 1
Reputation: 18908
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)
Upvotes: 22
Reputation: 117
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
Upvotes: 4
Reputation: 1490
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
Upvotes: 2
Reputation: 321
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
Upvotes: 0
Reputation: 6861
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
}
}
Upvotes: 0
Reputation: 92599
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:
StatusBar
will not appear in the final image.Upvotes: 71
Reputation: 2531
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
}
}
Upvotes: 6
Reputation: 552
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
Upvotes: 1
Reputation: 1476
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
}
}]
Upvotes: 2
Reputation: 12900
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....
Upvotes: 10
Reputation: 603
// 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
}
Upvotes: 1
Reputation: 1864
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!
Upvotes: 3
Reputation: 56645
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.
Upvotes: 89