Capturing full screenshot with status bar in iOS programmatically
Asked Answered
O

7

15

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?

Outage answered 14/12, 2011 at 16:55 Comment(0)
L
19

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.

Lazulite answered 14/12, 2011 at 18:11 Comment(3)
The link seems to be broken.Gimel
If you combine the two approaches, you will get a black image, apparently the snapshot view cannot be drawn this way. Even if you iterate through windows on screen, you cannot capture status bar. Sadly.Jannjanna
So I'm guessing the correct answer would be that there is ABSOLUTELY NO way to get a screenshot with the status bar?Playboy
I
7

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.
Ineffaceable answered 25/12, 2011 at 23:54 Comment(0)
M
6

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);
Mahaffey answered 3/3, 2013 at 9:11 Comment(3)
it work nice for me when i use to take screen shot of full screen with keyboard..Vidette
There is no status bar in these screenshotsUnexpressive
Despite the allegation of plagiarism, it's nice to have the answer here, in context, and not just a link that my or may not work in the future.Linkous
A
1

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
        }
    }
    
Airing answered 24/3, 2018 at 20:40 Comment(1)
Note: The above solution should add UIGraphicsEndImageContext() after getting the image to prevent a leak.Chirrup
B
1

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)

Bellona answered 13/8, 2019 at 0:43 Comment(0)
C
0

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()
}
Cancellate answered 7/3, 2019 at 16:0 Comment(0)
N
-1

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()
Nobility answered 5/3, 2016 at 2:50 Comment(3)
This DOES work well in the simulator but it doesn't seem to work on an iOS device. I get a black rectangle, even when I force it to the main thread.Glaswegian
@Glaswegian did you found any solution for sameHoseahoseia
Works great for the simulator, this solved my issue of generating App Store Screenshots automatically without having to migrate to XCUITest and injecting all the context via launch arguments!Greenlaw

© 2022 - 2024 — McMap. All rights reserved.