今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS Long Screenshot

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:

  1. Content not on the ScrollView, such as navigationBar and tabBar, is not visible when using UIScreenshotService.

  2. 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.

  3. If you use CAShapeLayer or similar to create rounded corners or other effects without setting the layer's fillColor, the final output from UIScreenshotService will appear pure black.

  4. 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.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.