如何在Swift中进行全屏截图?

81

我找到了这段代码

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

我需要做什么才能将其他元素(例如导航栏)也显示在截图中?


你知道如何在Objective-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()]; image = UIGraphicsGetImageFromCurrentImageContext();这段代码是用于在Objective-C中截取全屏截图的。如果要将其转换为Swift,请使用以下代码: - Mohit
谢谢,但是没有状态栏。 - Pietro La Spada
21个回答

88

不要只简单地给出答案,让我解释一下你当前的代码是如何工作的,以及如何修改它来捕获整个屏幕。

UIGraphicsBeginImageContext(view.frame.size)

这行代码创建一个与view大小相同的新图像上下文。重要的是要记住,新的图像上下文view大小相同。除非你想捕捉低分辨率(非Retina)版本的应用程序,否则你可能应该使用UIGraphicsBeginImageContextWithOptions,然后可以传递0.0以获得与设备主屏幕相同的比例因子。

view.layer.renderInContext(UIGraphicsGetCurrentContext())

这行代码会将视图层呈现到当前的图形上下文中(也就是您刚刚创建的上下文)。在这里需要注意的主要事项是,只有view(及其子视图)被绘制到图像上下文中。

let image = UIGraphicsGetImageFromCurrentImageContext()

这行代码从图形上下文中绘制的内容创建一个UIImage对象。

UIGraphicsEndImageContext()
这行代码结束了图像上下文。它会清理(因为您创建了上下文,所以也应该删除它)。
结果是一个与view相同大小的图像,其中包含了view及其子视图绘制的内容。
如果您想将所有东西都画进图像中,那么您应该创建一个与屏幕大小相同的图像,并将屏幕上的所有内容都画到其中。在实践中,您可能只关心应用程序的“关键窗口”中的所有内容。由于UIWindowUIView的子类,因此它也可以被绘制到图像上下文中。

当我尝试捕捉屏幕图像时,结果得到的图像有些像素化。我无法弄清原因。你有任何想法吗,David? - Sam
1
请注意,我没有更改任何 OP 的代码,只是给了正确方向的推动。您看到的像素化外观来自图像上下文的低比例因子。如果您阅读 UIGraphicsBeginImageContext 的文档,您将看到它调用 UIGraphicsBeginImageContextWithOptions 并使用比例因子为 1.0。阅读 文档将告诉您,您可以将比例因子设置为 0.0 以与您的主屏幕相同。 - David Rönnqvist
谢谢David。将比例因子设置为0确实起作用了。 - Sam
我知道这是一个旧的帖子,但有没有一种方法可以捕获屏幕上呈现的所有内容,包括警报? - u84six
@DavidRönnqvist,请问,是否真的可以在屏幕截图中捕捉键盘? - Alexander Khitev
1
@AlexanderKhitev 我认为这是不可能的,因为键盘不是您应用程序的一部分。如果您能够捕获整个屏幕的截图(例如,在拆分屏幕中使用两个应用程序的iPad),那将是一个隐私噩梦。 - ndreisg

71

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
    }

已更新至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,您需要将以下密钥添加到Info.plist中: <key>NSPhotoLibraryUsageDescription</key> <string>将其添加到您的照片库中</string> - user3069232
1
如何在截图中包含状态栏? - bLacK hoLE
1
如果类型是可选的,你不能返回nil。你的代码"guard let context = UIGraphicsGetCurrentContext() else {return nil}"是不正确的。 - George Asda
显示剩余4条评论

44

详细信息

  • 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,Swift 3

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

iOS 9x,10x的版本2

如果您尝试在iOS 9x中使用版本1代码,将会出现错误: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!

1
如果我想捕获AVPlayerViewController的自定义外观以及UIView,有什么建议吗?请提供意见! - Raghuram
我尝试了这段代码,但它没有捕获键盘,对吗? - Alexander Khitev
Swift 5 返回 nil。 - MartianMartian

22

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

2
这是针对特定视图截屏,而不是整个屏幕。 - Rodrigo Ruiz

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以捕获该视图的图像....


6

在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

4

Swift 5

在新设备(如 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
    }
}

以前使用UIApplication.shared.keyWindow?.layer.render(in: context)的解决方案会在某些设备上(如 iPhone 12 Pro)导致内存崩溃。


我想截取添加了gif的图像视图并保存。我想通过定时器实现对gif动画进行50次截屏。如何使用定时器进行截屏? - Emre Değirmenci
1
你从哪里获取keyWindow? - MartianMartian

4

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

我从问题中提取了代码,并遵循David Rönnqvist的建议(感谢您的解释),进行了一些调整,组成了这个答案。
如果要包括导航栏和其他额外内容,请从window而不是view调用此方法。
我只需要一个获取视图屏幕截图的函数,所以希望这可以帮助任何寻找相同功能的人。

什么是视图? - MartianMartian

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

它可以捕获除状态栏以外的所有内容,并且不需要许可即可保存图像到相机胶卷中。
希望这有所帮助!

3

Swift 5

如果您只需要屏幕的真实快照(包括键盘和状态栏):

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

snap 是一个 UIView,其框架自动设置为屏幕的边界。将其添加到视图控制器的视图中,现在会给你一个看起来像被冻结的 UI:

view.addSubview(snap)

场景更新

iOS应用程序现在采用了场景,这使得上述解决方案无效。您必须快照活动场景的主窗口,而不是快照主屏幕。

如果您的应用程序只有一个场景,则以下内容将起作用。但是,如果您有多个场景,则必须先找到活动场景(然后找到其主窗口)。

if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate,
   let snap = sceneDelegate.window?.snapshotView(afterScreenUpdates: false) {
    view.addSubview(snap)
}

对于有多个场景的应用程序:https://dev59.com/9FMI5IYBdhLWcg3wRZbJ#69780721


如果您的WKWebView正在显示AVPlayer,例如滑动mp4视频时,此功能将无法正常工作。 - zumzum
抱歉,我刚在IOS 16上尝试了一下,结果只显示黑屏。 - user3069232
@user3069232 这是因为现在的iOS应用程序依赖于场景。我已经更新了我的答案。 - trndjc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接