iOS长截图 背景 Twitter 上看到TaioApp的作者说,iOS 系统有支持长截图的API——UIScreenshotService ,从 iOS 13开始就可以使用,下午的时候就在自己的 APP 中体验了一下。
过程 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.
截屏时,使用UIScreenshotService 最终提供的是 PDF,需要通过UIScreenshotServiceDelegate生成 PDF data.
使用如下:
把方法处理封装到单独的类,通过方法传入 view,来决定截屏时使用那个 view 来生成 PDF data。
.h文件
1 2 3 4 5 6 7 8 9 10 11 #import <Foundation/Foundation.h> @interface WPSScreenShotHelper : NSObject + (instancetype )helper; - (void )configScreenShotHelper:(UIScrollView *)scrollView; @end
.m 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #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 )){ 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; NSMutableData *pdfData = [NSMutableData data]; UIGraphicsBeginPDFContextToData (pdfData, self .contentScrollView.frame, nil ); UIGraphicsBeginPDFPage (); CGContextRef pdfContext = UIGraphicsGetCurrentContext (); [self .contentScrollView.layer renderInContext:pdfContext]; UIGraphicsEndPDFContext (); self .contentScrollView.frame = frame; self .contentScrollView.contentOffset = contentOffset; self .contentScrollView.contentInset = contentInset; completionHandler(pdfData, 0 , CGRectZero ); } @end
使用UIScreenshotService 确实可以长截屏,截的内容的大小是ScrollView 的 contentSize。
但是有以下一些问题,需要注意:
不在 ScrollView 上的内容使用UIScreenshotService 时看不到,比如 navigationBar、tabBar。
webview 的截屏,通过获取 webview 的 scrollview 的高度,也可以截到。但是,如果是 webview 里面加载的网页中间或者网页部分使用了滑动,即不是 WebView 的 Scroll而是 H5网页里的 Scroll,这样的显示,UIScreenshotService 是获取不到的,因为最后不管怎么获取 webview 的 scrollview 的 contentSize 的Height一直都是那么高,滑动的部分不是这个 scrollview。
使用了CAShapeLayer之类的,生成圆角或者其他,如果没有设置 layer 的 fillColor,最终UIScreenshotService 显示出来会是纯黑的。
由于方法的回调依赖 scrollView 生成 PDF data,所以每个需要截图的界面,在进入时,都需要手动更新 contentScrollView,二级界面的还好说,可以hook scrollView 的初始化方法,保证每次创建时都更新 helper 的 contentScrollView,但是一级界面的几个 tab 切换,则只能通过手动更新的方式来设置(或者根据 tabIndex 获取不同的 scrollview),总之这也是需要注意的地方。
总结 UIScreenshotService 确实能生成长截图,对于项目结构相对简洁明了、代码比较规范、只需要某个原生页面支持长截图的 APP 来说,可以使用。 但是如果项目中 H5多,且项目结构复杂的话,使用就不太方便了。