今是昨非

今是昨非

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

iOS Long Screenshot

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:

  1. Content that is not on the ScrollView cannot be captured using UIScreenshotService, such as the navigationBar and tabBar.

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

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

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

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