以编程方式在 iOS 中捕获带有状态栏的完整屏幕截图

发布于 2024-12-21 11:18:36 字数 770 浏览 1 评论 0原文

我正在使用此代码来捕获屏幕截图并将其保存到相册中。

-(void)TakeScreenshotAndSaveToPhotoAlbum
{
   UIWindow *window = [UIApplication sharedApplication].keyWindow;

   if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
       UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, [UIScreen mainScreen].scale);
   else
       UIGraphicsBeginImageContext(window.bounds.size);

   [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();

   UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}

但问题是,每当保存屏幕截图时,我都会看到 iPhone 的状态栏没有被捕获。相反,底部会出现一个空白。就像下图一样: 在此处输入图像描述

我做错了什么?

I am using this code to capture a screenshot and to save it to the photo album.

-(void)TakeScreenshotAndSaveToPhotoAlbum
{
   UIWindow *window = [UIApplication sharedApplication].keyWindow;

   if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
       UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, [UIScreen mainScreen].scale);
   else
       UIGraphicsBeginImageContext(window.bounds.size);

   [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();

   UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}

But the problem is whenever the screenshot is saved, I see the status bar of iPhone is not captured. Instead a white space appears at the bottom. Like the following image:
enter image description here

What am I doing wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

深海蓝天 2024-12-28 11:18:36

状态栏实际上位于其自己的 UIWindow 中,在您的代码中,您仅渲染视图控制器的视图,其中不包含此视图。

“官方”截图方法是这里,但现在似乎已被Apple删除,可能是因为它已经过时了。

在 iOS 7 下,UIScreen 现在有一个新方法,用于获取包含整个屏幕内容的视图:

- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

这将为您提供一个视图,然后您可以在屏幕上操作该视图以获得各种视觉效果。

如果要将视图层次结构绘制到上下文中,则需要遍历应用程序的窗口 ([[UIApplication sharedApplication] windows]) 并在每个窗口上调用此方法:

- (BOOL)drawViewHierarchyInRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates

也许可以结合上述两种方法并获取快照视图,然后在快照上使用上述方法来绘制它。

The status bar is actually in its own UIWindow, in your code you are only rendering the view of your viewcontroller which does not include this.

The "official" screenshot method was here but now seems to have been removed by Apple, probably due to it being obsolete.

Under iOS 7 there is now a new method on UIScreen for getting a view holding the contents of the entire screen:

- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

This will give you a view which you can then manipulate on screen for various visual effects.

If you want to draw the view hierarchy into a context, you need to iterate through the windows of the application ([[UIApplication sharedApplication] windows]) and call this method on each one:

- (BOOL)drawViewHierarchyInRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates

You may be able to combine the two above approaches and take the snapshot view, then use the above method on the snapshot to draw it.

故人的歌 2024-12-28 11:18:36

建议的“官方”屏幕截图方法不会捕获状态栏(它不在应用程序的窗口列表中)。在 iOS 5 上进行了测试。

我相信,这是出于安全原因,但文档中没有提及。

我建议两个选项:

  • 从应用程序的资源中绘制存根状态栏图像(可以选择更新时间指示器);
  • 仅捕获您的视图,没有状态栏,或随后修剪图像(图像大小将与标准设备分辨率不同);状态栏框架由应用程序对象的相应属性可知。

The suggested "official" screenshot method doesn't capture status bar (it is not in the windows list of the application). As tested on iOS 5.

I believe, this is for security reasons, but there is no mention of it in the docs.

I suggest two options:

  • draw a stub status bar image from resources of your app (optionally update time indicator);
  • capture only your view, without status bar, or trim image afterwards (image size will differ from standard device resolution); status bar frame is known from corresponding property of application object.
装纯掩盖桑 2024-12-28 11:18:36

这是我的代码,用于截取屏幕截图并将其存储为 NSData(在 IBAction 内)。有了旧的 NSData,你就可以分享或发送电子邮件或任何想做的事情

CGSize imageSize = [[UIScreen mainScreen] bounds].size;
        if (NULL != UIGraphicsBeginImageContextWithOptions)
            UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
        else
            UIGraphicsBeginImageContext(imageSize);

        CGContextRef context = UIGraphicsGetCurrentContext();

        // Iterate over every window from back to front
        for (UIWindow *window in [[UIApplication sharedApplication] windows])
        {
            if (![window respondsToSelector:@selector(screen)] || [window screen] == [UIScreen mainScreen])
            {
                // -renderInContext: renders in the coordinate space of the layer,
                // so we must first apply the layer's geometry to the graphics context
                CGContextSaveGState(context);
                // Center the context around the window's anchor point
                CGContextTranslateCTM(context, [window center].x, [window center].y);
                // Apply the window's transform about the anchor point
                CGContextConcatCTM(context, [window transform]);
                // Offset by the portion of the bounds left of and above the anchor point
                CGContextTranslateCTM(context,
                                      -[window bounds].size.width * [[window layer] anchorPoint].x,
                                      -[window bounds].size.height * [[window layer] anchorPoint].y);

                // Render the layer hierarchy to the current context
                [[window layer] renderInContext:context];

                // Restore the context
                CGContextRestoreGState(context);
            }
        }

        // Retrieve the screenshot image
        UIImage *imageForEmail = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

    NSData *imageDataForEmail = UIImageJPEGRepresentation(imageForEmail, 1.0);

Here is my code to take a screenshot and store it as NSData (inside an IBAction). With the sotred NSData then you can share or email or whatever want to do

CGSize imageSize = [[UIScreen mainScreen] bounds].size;
        if (NULL != UIGraphicsBeginImageContextWithOptions)
            UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
        else
            UIGraphicsBeginImageContext(imageSize);

        CGContextRef context = UIGraphicsGetCurrentContext();

        // Iterate over every window from back to front
        for (UIWindow *window in [[UIApplication sharedApplication] windows])
        {
            if (![window respondsToSelector:@selector(screen)] || [window screen] == [UIScreen mainScreen])
            {
                // -renderInContext: renders in the coordinate space of the layer,
                // so we must first apply the layer's geometry to the graphics context
                CGContextSaveGState(context);
                // Center the context around the window's anchor point
                CGContextTranslateCTM(context, [window center].x, [window center].y);
                // Apply the window's transform about the anchor point
                CGContextConcatCTM(context, [window transform]);
                // Offset by the portion of the bounds left of and above the anchor point
                CGContextTranslateCTM(context,
                                      -[window bounds].size.width * [[window layer] anchorPoint].x,
                                      -[window bounds].size.height * [[window layer] anchorPoint].y);

                // Render the layer hierarchy to the current context
                [[window layer] renderInContext:context];

                // Restore the context
                CGContextRestoreGState(context);
            }
        }

        // Retrieve the screenshot image
        UIImage *imageForEmail = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

    NSData *imageDataForEmail = UIImageJPEGRepresentation(imageForEmail, 1.0);
我还不会笑 2024-12-28 11:18:36

上述问题的 Objective-C 答案已经写在那里,这里是上述问题的 Swift 版本答案。

对于 Swift 3+

截取屏幕截图,然后用它来显示某处或通过网络发送。

extension UIImage {
    class var screenShot: UIImage? {
        let imageSize = UIScreen.main.bounds.size as CGSize;
        UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
        guard let context = UIGraphicsGetCurrentContext() else {return nil}
        for obj : AnyObject in UIApplication.shared.windows {
            if let window = obj as? UIWindow {
                if window.responds(to: #selector(getter: UIWindow.screen)) || window.screen == UIScreen.main {
                    // so we must first apply the layer's geometry to the graphics context
                    context.saveGState();
                    // Center the context around the window's anchor point
                    context.translateBy(x: window.center.x, y: window.center
                        .y);
                    // Apply the window's transform about the anchor point
                    context.concatenate(window.transform);
                    // Offset by the portion of the bounds left of and above the anchor point
                    context.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x,
                                         y: -window.bounds.size.height * window.layer.anchorPoint.y);

                    // Render the layer hierarchy to the current context
                    window.layer.render(in: context)

                    // Restore the context
                    context.restoreGState();
                }
            }
        }
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return nil}
        return image
    }
}

使用上面的屏幕截图

  • 让我们在 UIImageView 上显示上面的屏幕截图

    yourImageView = UIImage.screenShot
    
  • 获取图像数据以通过网络保存/发送

    if let img = UIImage.screenShot {
        如果让数据 = UIImagePNGRepresentation(img) {
            //通过网络发送此数据或将其存储在任何地方
        }
    }
    

Answer of above question for Objective-C is already write there, here is the Swift version answer of above question.

For Swift 3+

Take screenshot and then use it to display somewhere or to send over web.

extension UIImage {
    class var screenShot: UIImage? {
        let imageSize = UIScreen.main.bounds.size as CGSize;
        UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
        guard let context = UIGraphicsGetCurrentContext() else {return nil}
        for obj : AnyObject in UIApplication.shared.windows {
            if let window = obj as? UIWindow {
                if window.responds(to: #selector(getter: UIWindow.screen)) || window.screen == UIScreen.main {
                    // so we must first apply the layer's geometry to the graphics context
                    context.saveGState();
                    // Center the context around the window's anchor point
                    context.translateBy(x: window.center.x, y: window.center
                        .y);
                    // Apply the window's transform about the anchor point
                    context.concatenate(window.transform);
                    // Offset by the portion of the bounds left of and above the anchor point
                    context.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x,
                                         y: -window.bounds.size.height * window.layer.anchorPoint.y);

                    // Render the layer hierarchy to the current context
                    window.layer.render(in: context)

                    // Restore the context
                    context.restoreGState();
                }
            }
        }
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return nil}
        return image
    }
}

Usage of above screenshot

  • Lets display above screen shot on UIImageView

    yourImageView = UIImage.screenShot
    
  • Get image Data to save/send over web

    if let img = UIImage.screenShot {
        if let data = UIImagePNGRepresentation(img) {
            //send this data over web or store it anywhere
        }
    }
    
幸福丶如此 2024-12-28 11:18:36

Swift、iOS 13:

下面的代码(以及其他访问方式)现在将使应用程序崩溃并显示一条消息:

应用程序在 UIApplication 上调用 -statusBar 或 -statusBarWindow:必须更改此代码,因为不再有状态栏或状态栏窗口。请改用窗口场景上的 statusBarManager 对象。

窗口场景和 statusBarManager 实际上只允许我们访问框架 - 如果这仍然可能,我不知道如何实现。

Swift,iOS10-12:

以下内容对我有用,并且在分析了捕获编程屏幕截图的所有方法之后 - 这是最快的,也是 Apple 在 iOS 10 之后推荐的方法

let screenshotSize = CGSize(width: UIScreen.main.bounds.width * 0.6, height: UIScreen.main.bounds.height * 0.6)
let renderer = UIGraphicsImageRenderer(size: screenshotSize)
let statusBar = UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow
let screenshot = renderer.image { _ in
    UIApplication.shared.keyWindow?.drawHierarchy(in: CGRect(origin: .zero, size: screenshotSize), afterScreenUpdates: true)
    statusBar?.drawHierarchy(in: CGRect(origin: .zero, size: screenshotSize), afterScreenUpdates: true)
}

您不必缩小屏幕截图尺寸(您如果需要,可以直接使用 UIScreen.main.bounds

Swift, iOS 13:

The code below (and other ways of accessing) will now crash the app with a message:

App called -statusBar or -statusBarWindow on UIApplication: this code must be changed as there's no longer a status bar or status bar window. Use the statusBarManager object on the window scene instead.

The window scenes and statusBarManager's really only give us access to frame - if this is still possible, I am not aware how.

Swift, iOS10-12:

The following works for me, and after profiling all the methods for capturing programmatic screenshots - this is the quickest, and the recommended way from Apple following iOS 10

let screenshotSize = CGSize(width: UIScreen.main.bounds.width * 0.6, height: UIScreen.main.bounds.height * 0.6)
let renderer = UIGraphicsImageRenderer(size: screenshotSize)
let statusBar = UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow
let screenshot = renderer.image { _ in
    UIApplication.shared.keyWindow?.drawHierarchy(in: CGRect(origin: .zero, size: screenshotSize), afterScreenUpdates: true)
    statusBar?.drawHierarchy(in: CGRect(origin: .zero, size: screenshotSize), afterScreenUpdates: true)
}

You don't have to scale your screenshot size down (you can use UIScreen.main.bounds directly if you want)

反差帅 2024-12-28 11:18:36

截取iPhone全屏,使用KVC获取状态栏:

if let snapView = window.snapshotView(afterScreenUpdates: false) {
    if let statusBarSnapView = (UIApplication.shared.value(forKey: "statusBar") as? UIView)?.snapshotView(afterScreenUpdates: false) {
        snapView.addSubview(statusBarSnapView)
    }
    UIGraphicsBeginImageContextWithOptions(snapView.bounds.size, true, 0)
    snapView.drawHierarchy(in: snapView.bounds, afterScreenUpdates: true)
    let snapImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

Capture the full screen of iPhone, get the status bar by using KVC:

if let snapView = window.snapshotView(afterScreenUpdates: false) {
    if let statusBarSnapView = (UIApplication.shared.value(forKey: "statusBar") as? UIView)?.snapshotView(afterScreenUpdates: false) {
        snapView.addSubview(statusBarSnapView)
    }
    UIGraphicsBeginImageContextWithOptions(snapView.bounds.size, true, 0)
    snapView.drawHierarchy(in: snapView.bounds, afterScreenUpdates: true)
    let snapImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}
花落人断肠 2024-12-28 11:18:36

以下对我有用,捕获状态栏很好(iOS 9,Swift)

let screen = UIScreen.mainScreen()
let snapshotView = screen.snapshotViewAfterScreenUpdates(true)
UIGraphicsBeginImageContextWithOptions(snapshotView.bounds.size, true, 0)
snapshotView.drawViewHierarchyInRect(snapshotView.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

The following works for me, capturing the status bar fine (iOS 9, Swift)

let screen = UIScreen.mainScreen()
let snapshotView = screen.snapshotViewAfterScreenUpdates(true)
UIGraphicsBeginImageContextWithOptions(snapshotView.bounds.size, true, 0)
snapshotView.drawViewHierarchyInRect(snapshotView.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文