iOS Long Screenshots#
Background#
I saw on Twitter that the author of TaioApp mentioned that iOS has an API that supports long screenshots—UIScreenshotService, which has been available since iOS 13. I tried it out in my own app in the afternoon.
Process#
The official description of UIScreenshotService is as follows:
When the user takes a screenshot of your app's content, you work with a UIScreenshotService object to provide a PDF version of that screenshot. You do not create a UIScreenshotService object directly. Instead, you retrieve the object from the screenshotService property of your window scene and assign a delegate to it. When the user takes a screenshot, UIKit asks your delegate for the PDF data.
When taking a screenshot, the final output using UIScreenshotService is a PDF, which needs to be generated through UIScreenshotServiceDelegate.
Usage is as follows:
Encapsulate the method handling into a separate class, passing in the view through the method to determine which view to use for generating the PDF data during the screenshot.
.h file
#import <Foundation/Foundation.h>
@interface WPSScreenShotHelper : NSObject
+ (instancetype)helper;
- (void)configScreenShotHelper:(UIScrollView *)scrollView;
@end
.m file
#import "MKScreenShotHelper.h"
@interface MKScreenShotHelper ()<UIScreenshotServiceDelegate>
@property (nonatomic, strong) UIScrollView *contentScrollView;
@end
@implementation MKScreenShotHelper
+ (instancetype)helper {
static WPSScreenShotHelper *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == nil) {
instance = [WPSScreenShotHelper new];
}
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
if (@available(iOS 13.0, *)) {
[UIApplication sharedApplication].keyWindow.windowScene.screenshotService.delegate = self;
}
}
return self;
}
- (void)configScreenShotHelper:(UIScrollView *)scrollView {
self.contentScrollView = scrollView;
}
#pragma mark - UIScreenshotServiceDelegate
- (void)screenshotService:(UIScreenshotService *)screenshotService generatePDFRepresentationWithCompletion:(void (^)(NSData * _Nullable, NSInteger, CGRect))completionHandler API_AVAILABLE(ios(13.0)){
// Temporarily change the size of the ScrollView to ensure it renders completely
CGRect frame = self.contentScrollView.frame;
CGPoint contentOffset = self.contentScrollView.contentOffset;
UIEdgeInsets contentInset = self.contentScrollView.contentInset;
CGRect toFrame = frame;
toFrame.size = self.contentScrollView.contentSize;
self.contentScrollView.frame = toFrame;
// Render the scrollView to PDF
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, self.contentScrollView.frame, nil);
UIGraphicsBeginPDFPage();
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
[self.contentScrollView.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
// Restore the scrollView's frame
self.contentScrollView.frame = frame;
self.contentScrollView.contentOffset = contentOffset;
self.contentScrollView.contentInset = contentInset;
// Callback with the result
completionHandler(pdfData, 0, CGRectZero);
}
@end
Using UIScreenshotService can indeed create long screenshots, and the size of the captured content is the contentSize of the ScrollView.
However, there are some issues to note:
-
Content not on the ScrollView, such as navigationBar and tabBar, is not visible when using UIScreenshotService.
-
For screenshots of webviews, you can capture them by obtaining the height of the webview's scrollview. However, if the webpage loaded inside the webview has scrolling (i.e., not the WebView's Scroll but the scroll within the H5 webpage), UIScreenshotService cannot capture it, as the contentSize height of the webview's scrollview remains constant, and the scrolling part is not part of this scrollview.
-
If you use
CAShapeLayeror similar to create rounded corners or other effects without setting the layer's fillColor, the final output from UIScreenshotService will appear pure black. -
Since the method callback relies on the scrollView to generate PDF data, each interface that needs a screenshot must manually update the contentScrollView upon entry. For secondary interfaces, this can be managed by hooking into the scrollView's initialization method to ensure the helper's contentScrollView is updated each time it is created. However, for the primary interface's tab switches, this can only be set through manual updates (or by obtaining different scrollviews based on tabIndex). This is another point to keep in mind.
Summary#
UIScreenshotService can indeed generate long screenshots. For projects with a relatively simple structure, well-organized code, and where only a specific native page needs to support long screenshots, it can be used. However, if the project has a lot of H5 content and a complex structure, its use may not be very convenient.