iOS Long Screenshots#
Background#
I saw on Twitter that the author of TaioApp mentioned that iOS has an API called UIScreenshotService that supports long screenshots. It has been available since iOS 13, so I tried it out in my own app this afternoon.
Process#
According to the official documentation of UIScreenshotService:
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, UIScreenshotService provides a PDF version, which needs to be generated as PDF data through UIScreenshotServiceDelegate
.
Here's how to use it:
Encapsulate the method into a separate class and pass in the view to determine which view to use for generating PDF data when taking a 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 that it is fully rendered
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 as PDF
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, self.contentScrollView.frame, nil);
UIGraphicsBeginPDFPage();
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
[self.contentScrollView.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
// Restore the frame of the scrollView
self.contentScrollView.frame = frame;
self.contentScrollView.contentOffset = contentOffset;
self.contentScrollView.contentInset = contentInset;
// Callback with the result
completionHandler(pdfData, 0, CGRectZero);
}
@end
Using UIScreenshotService does allow for capturing long screenshots, where the size of the captured content is based on the contentSize of the ScrollView.
However, there are a few things to note:
-
Content that is not on the ScrollView cannot be captured using UIScreenshotService, such as the navigationBar and tabBar.
-
To capture screenshots of webviews, you can obtain the height of the webview's scrollview and capture it. However, if the web page loaded inside the webview has its own scroll (not the webview's scroll), UIScreenshotService cannot capture it because the contentSize's height of the scrollview is always the same, and the scrollable part is not part of this scrollview.
-
If you use
CAShapeLayer
or similar to create rounded corners or other effects, if the layer's fillColor is not set, the result displayed by UIScreenshotService will be pure black. -
Since the callback of the method depends on the scrollView to generate the PDF data, each screen that needs to be captured requires manual updating of the contentScrollView. For secondary screens, it is relatively easy as you can hook into the initialization method of the scrollView to ensure that the contentScrollView of the helper is updated every time it is created. However, for primary screens with multiple tab switches, you can only set it manually (or obtain different scrollviews based on the tabIndex). This is something to be aware of.
Conclusion#
UIScreenshotService can indeed generate long screenshots and can be used for apps with relatively simple project structures, well-organized code, and only require long screenshot support for certain native pages. However, if the project has a lot of H5 content and a complex project structure, it may not be very convenient to use.