1. 需求說明
由于最近產品要求能夠將網頁轉換為一整張圖片,預估整個網頁的高度大概有10多個屏幕的高度,因此需要添加一個網頁轉換為圖片的功能。
如果是外部網頁,可以用 Safari 瀏覽器的截長屏功能(需要iOS 13.0+)。但是,這次需要截取的內容是通過 APP 內的 UIWebView 顯示的 HTML 網頁,這就無法通過 iOS 自帶的截屏功能實現了(都9012年了,iOS 怎么還沒有截長屏功能?)。
【相關鏈接:[iOS] UIScrollView (UIWebView) 截長屏功能實現】
- 方案簡述
繪制上下文生成圖片,可以通過UIGraphics
或者CoreGraphics
實現。這兩種方案的本質都是對 UIScrollView 進行截圖操作,因此可以將這個實現方案類推至 UITableView 、 UICollectionView 等基于 UIScrollView 的控件中。
UIGraphics
實現起來比較簡單,但是需要注意運行內存的釋放
問題。
CoreGraphics
速度快,占用空間手動釋放,還可以使用異步實現,但是要花時間理解 CoreGraphics 的相關函數,需要對 CoreGraphics 繪制的圖片進行翻轉(這就涉及到 CoreGraphics 坐標系的問題)。
這兩種方案中,更推薦使用 CoreGraphics 來實現截屏功能
。
另外,在使用這兩種方案的調試過程中,一定要時刻關注運行內存的變化。使用 iPhone 6 測試,當高度達到 20000 + 的時候,內存都會達到 450M 左右(上限 650M)。
- 方案實現
(1) 設定 WebView
@interface ViewController ()
@property(nonatomic, strong) UIWebView *webView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
// 百度的首頁可以一直往下拉,剛好可以用來測試截長屏的功能
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];
[self.view addSubview:_webView];
}
(2) 添加截取圖片的功能
使用UIGraphics
截取圖片
- (UIImage *)getImageByUIGraphic {
// 為了防止內存不能被及時釋放,導致系統內存告急,因此在外面加一層自動釋放池
@autoreleasepool {
// 設置截圖大小
UIScrollView *scrollView = self.webView.scrollView;
// 保存當前的偏移量
CGPoint previousContentOffset = scrollView.contentOffset;
CGRect previousFrame = scrollView.frame;
// 將偏移量設置為(0,0)
scrollView.contentOffset = CGPointZero;
scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
NSLog(@"-------- %f, %f", scrollView.frame.size.height, scrollView.contentSize.height);
// ---------- start -------------
// 指定大小、透明度和縮放
UIGraphicsBeginImageContextWithOptions(scrollView.contentSize, YES, 0);
// 在當前上下文中渲染出整個內容
[scrollView.layer renderInContext:UIGraphicsGetCurrentContext()];
// 截取當前上下文,生成Image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉圖形上下文
UIGraphicsEndImageContext();
// ---------- end --------------
// 注意:恢復偏移量
scrollView.contentOffset = previousContentOffset;
scrollView.frame = previousFrame;
return image;
}
}
使用CoreGraphics
截取圖片
- (UIImage *)getImageByCoreGraphics {
// 設置截圖大小
UIScrollView *scrollView = self.webView.scrollView;
// 保存當前的偏移量
CGPoint previousContentOffset = scrollView.contentOffset;
CGRect previousFrame = scrollView.frame;
// 將偏移量設置為(0,0)
scrollView.contentOffset = CGPointZero;
scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
// ---------- start -------------
// 申請繪制空間
unsigned char *imageBuffer = (unsigned char *)malloc(4 * scrollView.contentSize.width * scrollView.contentSize.height);
// 繪制上下文
CGContextRef imageContext = CGBitmapContextCreate(NULL, scrollView.contentSize.width, scrollView.contentSize.height, 8, scrollView.contentSize.width * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast);
// 將CoreGraphics轉換成UIKit的坐標系,先向下移動整張圖片的高度,然后垂直翻轉
CGContextTranslateCTM(imageContext, 0.0f, scrollView.contentSize.height);
CGContextScaleCTM(imageContext, 1.0f, -1.0f);
// 渲染全部內容
[scrollView.layer renderInContext:imageContext];
// 生成圖片
CGImageRef imageRef = CGBitmapContextCreateImage(imageContext);
// 轉換為UIImage
UIImage *image = [UIImage imageWithCGImage:imageRef];
// 注意:釋放申請的空間
CGImageRelease(imageRef);
CGContextRelease(imageContext);
free(imageBuffer);
// ---------- end --------------
// 注意:恢復偏移量
scrollView.contentOffset = previousContentOffset;
scrollView.frame = previousFrame;
return image;
}
(3) 添加保存圖片到相冊的功能
在實現這個功能之前,需要在info.plist中添加“Privacy - Photo Library Usage Description”,否則會造成 APP 崩潰。
// 記得添加頭文件
// #import <Photos/Photos.h>
/// 保存圖片到照片庫
- (void)saveImage:(UIImage *)image {
NSLog(@"saveImage: %@",image);
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:image];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (error) {
NSLog(@"保存失敗: %@", error);
} else {
NSLog(@"保存成功");
}
}];
}
(4) 綁定截圖按鈕事件
- (IBAction)buttonEvent:(id)sender {
//UIImage *image = [self getImageByUIGraphic];
UIImage *image = [self getImageByCoreGraphics];
if (!image) {
NSLog(@"image is nil");
return;
}
// 校驗權限,保存圖片
PHAuthorizationStatus photoLibraryStatus = [PHPhotoLibrary authorizationStatus];
if (photoLibraryStatus != PHAuthorizationStatusAuthorized) {
// 權限不足
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status != PHAuthorizationStatusAuthorized) {
// 用戶仍然不同意程序訪問相冊
NSLog(@"權限不足");
} else {
[self saveImage:image];
}
}];
} else {
[self saveImage:image];
}
}
- 方案效果圖
在測試的過程中,發現有幾次截取的圖片最下面的部分為空白。經過反復測試,發現當百度網頁中頂部的導航欄(包含“推薦”、“視頻”、“娛樂”等選項的橫向導航欄)消失的時候(即處于網頁的頂部一定區域內),截圖的下半部分全是空白,造成這個現象的具體原因尚未弄清。
下面比較一下幾種方式生成的效果圖。圖(1)是通過 UIGraphic 方法繪制的圖片,圖(2)是通過 CoreGraphics 方法繪制的圖片,圖(3)是使用 Safari 瀏覽器中整頁截圖生成的 PDF ,轉換而成的圖片。