如何在Swift中拍摄全屏截图?


73

我找到了以下代码

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)
}

要使所有其他元素(如导航栏)进入屏幕截图,我需要做什么?


您知道如何在目标C中拍摄全屏屏幕截图吗?
Mohit

是的,但我不知道相关代码。
Pietro La Spada

CALayer * layer = [[UIApplication sharedApplication] keyWindow] .layer; CGFloat scale = [UIScreen mainScreen] .scale; UIGraphicsBeginImageContextWithOptions(layer.frame.size,NO,scale); [layer renderInContext:UIGraphicsGetCurrentContext()]; 图片= UIGraphicsGetImageFromCurrentImageContext(); 这是为了将目标C中的全屏屏幕截图转换为标准速度
Mohit14年

谢谢,但是没有StatusBar。
Pietro La Spada

Answers:


81

让我解释一下当前代码的功能以及如何修改它以捕获全屏屏幕,而不是仅仅把答案扔在那里。

UIGraphicsBeginImageContext(view.frame.size)

这行代码将创建一个新的图片上下文,其大小与相同view。这里要摘录的主要内容是新图像上下文的大小与相同view。除非您要捕获应用程序的低分辨率(非视网膜)版本,否则可能应该使用它UIGraphicsBeginImageContextWithOptions。然后,您可以通过0.0获得与设备主屏幕相同的比例因子。

view.layer.renderInContext(UIGraphicsGetCurrentContext())

这行代码会将视图的图层渲染到当前的图形上下文(即您刚刚创建的上下文)中。这里要view摘录的主要内容是仅(及其子视图)被绘制到图像上下文中。

let image = UIGraphicsGetImageFromCurrentImageContext()

此行代码根据绘制到图形上下文中的内容创建一个UIImage对象。

UIGraphicsEndImageContext()

这行代码结束了图像上下文。它已经清理了(您创建了上下文,也应该将其删除。


其结果是,其大小与相同的图像view,用view和它的子视图吸入到它。

如果要将所有内容绘制到图像中,则应创建与屏幕大小相同的图像,然后将屏幕上的所有内容绘制到图像中。实际上,您可能只是在谈论应用程序“键窗口”中的所有内容。由于UIWindow是的子类UIView,因此也可以将其绘制到图像上下文中。


当我尝试以此捕获屏幕上的图像时,结果是图像有点像素化。我不知道为什么。你有戴维的主意吗?
2015年

1
注意,我没有更改任何OP代码,只是朝正确的方向进行了推动。您看到的像素化外观来自图像上下文的低比例因子。如果您阅读该文档,UIGraphicsBeginImageContext将会看到它UIGraphicsBeginImageContextWithOptions的缩放比例为1.0。阅读文档将告诉您可以将比例因子设置0.0为与主屏幕相同。
DavidRönnqvist,2015年

谢谢大卫。实际上,将比例因子设置为0是可行的。
2015年

我知道这是一个旧线程,但是有没有办法捕获屏幕上呈现的所有内容,包括警报?
u84six

1
@AlexanderKhitev我认为这不可能,因为键盘不是您的应用程序的一部分。如果您能够捕获整个屏幕的屏幕截图(例如iPad上有两个分屏的应用程序),那将是一场隐私噩梦。
ndreisg,

62

斯威夫特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
    }

为Swift 2更新

您提供的代码有效,但不允许您捕获屏幕快照中的NavigationBarStatusBar。如果要截取包含的设备的屏幕截图,则NavigationBar必须使用以下代码:

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)
}

使用此代码:

  • 首次启动应用程序并调用此方法时,iOS设备将询问您将图像保存在相机胶卷中的权限。
  • 该代码的结果将是.JPG图像。
  • StatusBar不会出现在最终图像。

3
很棒的方法!由于比例尺,它可以以全视网膜分辨率捕获图像。非常感谢您提供此代码!Yan-
Fox5150

1
不幸的是,这根本不包括您已应用于整个图像中任何图层的任何CATransform3D。
Fattie

1
在iOS 10.x下使用swift 3.0,您需要将键<key> NSPhotoLibraryUsageDescription </ key> <string>添加到照片库</ string>添加到Info.plist
user3069232

1
如何在屏幕截图中也包含状态栏?
blak hoLE

1
不是真正正确的,因为如果类型是可选的,则您不能返回nil,那么您的行“ guard let context = UIGraphicsGetCurrentContext()else {return nil}”是不正确的
George Asda

36

细节

  • Xcode 9.3,Swift 4.1
  • Xcode 10.2(10E125)和11.0(11A420a),Swift 5

在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,快速3

适用于iOS 10x的版本1

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
    }
}

适用于iOS 9x,10x的版本2

如果您尝试在iOS 9x中使用版本1代码, 则会出现错误:CGImageCreateWithImageProvider:无效的图像提供程序: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!

1
如果我想与UIView一起捕获AVPlayerViewController的自定义外观,该怎么办?任何建议请!
Raghuram'2

我尝试了这段代码,但是它没有捕获键盘,对吧?
亚历山大·基特夫

22

迅速UIImage扩展:

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)
    }

}

用法:

let myImage: UIImage? = UIImage(view: myView)

1
这是针对特定视图而不是整个屏幕的屏幕截图。
罗德里戈·鲁伊斯

10

为方便起见,我将在自己的文件中添加扩展名

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
  }
}

如下调用扩展名...

let window: UIWindow! = UIApplication.sharedApplication().keyWindow

let windowImage = window.capture()

同样,可以扩展UIView来捕获该图像。


5

在iOS 10中创建上下文的推荐方法是使用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
    }
}

效果很好,但是在可滚动视图(例如集合视图)的情况下,生成的图像是第一个视图,而不是实际的当前视图。
markhorrocks

3

我使用这种方法:

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;
}

它捕获状态栏以外的所有内容,并且不要求将图像保存在相机胶卷中的权限。

希望能帮助到你!


2

UIWindow的Swift 3扩展

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

  }
}]

2

这就是我在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()

现在屏幕截图将是UIImage类型


2

适用于Swift 5iOS 13

对于正在寻找功能的快速答案以将视图的屏幕截图作为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
}

我整理了问题代码,并遵循DavidRönnqvist的建议(感谢您的解释)进行了一些调整,从而拼凑出了这个答案。

要包括导航栏和其他功能,请在窗口之外(而不是view)调用此方法。

我只需要一个函数来获取视图的屏幕截图,所以我希望这可以帮助寻找相同视图的任何人


1
  // 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
  }


1

该代码是最新版本-100%有效

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!;
}

1

迅捷5

如果您只需要那一瞬间的屏幕快照(带有键盘和状态栏):

let snap = UIScreen.main.snapshotView(afterScreenUpdates: false)

snap是一个UIView自动设置为屏幕边界的框架。现在将其添加到视图控制器的视图中将为您提供一个显示为冻结状态的UI:

view.addSubview(snap)

不需要扩展和所有不必要的东西-两行代码就足够了。


例如,当您滑动mp4视频时,如果您有一个显示AVPlayer的WKWebView,则此方法将不起作用。
zumzum


0

我的版本还捕获了一个键盘。斯威夫特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
    }

}

但是它没有捕获状态栏,有什么建议吗?
帕万

1
@Pavan我不知道,因为在我当前的应用程序中没有状态栏。
亚历山大·基捷夫

我在示例代码上进行了尝试,就像通过power + home按钮模仿Screenshot,但是此代码无法捕获状态栏。
帕万

0

Swift 4或以上。

对于扩展和通过捕获的UIView进行调用。

宣言

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

    }
}

用法

var m_image = UIImage()

if let tempCaptureImg = self.m_Capture_View.viewCapture() {
    viewController.m_image = tempCaptureImg
}

// m_Capture_View是UIView的类型

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.