今是昨非

今是昨非

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

iOS 長スクリーンショット

iOS 長スクリーンショット#

背景#

Twitter で TaioApp の作者が、iOS システムには長スクリーンショットをサポートする API——UIScreenshotServiceがあると述べており、iOS 13 から使用可能であることを知り、午後に自分の APP で体験してみました。

プロセス#

UIScreenshotServiceの公式説明は以下の通りです:

ユーザーがアプリのコンテンツのスクリーンショットを撮ると、UIScreenshotService オブジェクトを使用してそのスクリーンショットの PDF バージョンを提供します。UIScreenshotService オブジェクトを直接作成することはありません。代わりに、ウィンドウシーンの screenshotService プロパティからオブジェクトを取得し、それにデリゲートを割り当てます。ユーザーがスクリーンショットを撮ると、UIKit はデリゲートに PDF データを要求します。

スクリーンショットを撮る際、UIScreenshotServiceを使用すると最終的に提供されるのは PDF であり、UIScreenshotServiceDelegateを介して PDF データを生成する必要があります。

以下のように使用します:

メソッド処理を別のクラスにカプセル化し、メソッドを通じて view を渡して、スクリーンショットを撮る際にどの view を使用して PDF データを生成するかを決定します。

.h ファイル


#import <Foundation/Foundation.h>

@interface WPSScreenShotHelper : NSObject

+ (instancetype)helper;

- (void)configScreenShotHelper:(UIScrollView *)scrollView;

@end

.m ファイル


#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)){
    
    // ScrollViewのサイズを一時的に変更し、ScrollViewが完全にレンダリングされることを保証します
    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;
    
    // scrollViewをPDFとしてレンダリング
    NSMutableData *pdfData = [NSMutableData data];
    UIGraphicsBeginPDFContextToData(pdfData, self.contentScrollView.frame, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();
    [self.contentScrollView.layer renderInContext:pdfContext];
    UIGraphicsEndPDFContext();
    
    // scrollViewのフレームを元に戻す
    self.contentScrollView.frame = frame;
    self.contentScrollView.contentOffset = contentOffset;
    self.contentScrollView.contentInset = contentInset;
    
    // 結果をコールバック
    completionHandler(pdfData, 0, CGRectZero);
}

@end

UIScreenshotServiceを使用すると、確かに長スクリーンショットが可能で、キャプチャされる内容のサイズは ScrollView の contentSize です。

しかし、以下のいくつかの問題に注意が必要です:

  1. ScrollView 上にないコンテンツは、UIScreenshotServiceを使用しても表示されません。例えば、navigationBar や tabBar などです。

  2. webview のスクリーンショットは、webview の scrollview の高さを取得することでキャプチャできます。ただし、webview 内で読み込まれたウェブページの中間部分やウェブページの一部がスクロールを使用している場合、つまり WebView の Scroll ではなく H5 ウェブページ内の Scroll の場合、その表示はUIScreenshotServiceでは取得できません。なぜなら、最終的に webview の scrollview の contentSize の高さは常に同じで、スクロール部分はこの scrollview ではないからです。

  3. CAShapeLayerなどを使用して角丸やその他を生成する場合、layer の fillColor が設定されていないと、最終的にUIScreenshotServiceで表示されるのは真っ黒になります。

  4. メソッドのコールバックが scrollView に依存して PDF データを生成するため、各スクリーンショットが必要な画面では、入る際に手動で contentScrollView を更新する必要があります。二次画面はまだ良いですが、scrollView の初期化メソッドをフックして、毎回作成時に helper の contentScrollView を更新することができます。しかし、一次画面のいくつかのタブ切り替えは、手動で更新する方法(または tabIndex に基づいて異なる scrollview を取得する)で設定する必要があります。いずれにせよ、これは注意が必要な点です。

まとめ#

UIScreenshotServiceは確かに長スクリーンショットを生成できます。プロジェクト構造が比較的シンプルで、コードが比較的規範的で、特定のネイティブページが長スクリーンショットをサポートする APP には使用できます。しかし、プロジェクト内に H5 が多く、プロジェクト構造が複雑な場合、使用するのはあまり便利ではありません。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。