5 年后,我设法解决了最初的问题,这是由于MacOS 11实施WKWebView
printOperationWithPrintInfo
仍然无法正确处理滚动出视图和向右滚动的内容。
根本问题似乎是剪辑区域边界之外的内容(尤其是右侧)没有得到正确处理。这可能是一个WKWebView
错误,因为它似乎在垂直方向上处理可见矩形下方的一些内容。
经过大量挖掘,看到其他人能够通过以下NSView
方式打印并正确分页的全部内容:
- 视图分离(不在屏幕上)。
- 将框架设置为整个内容的大小。
- 然后在分离视图上调用printWithPrintInfo 。
我有一个解决方案的想法:
WKWebView
通过具有将所有内容作为图像图块获取的功能的类别进行扩展。它在 MacOS 上通过 JavaScript 和在 iOS 上执行此操作,方法是操作与UIScrollView
关联的WKWebView
以获得完整的内容大小,然后将内容的各个部分滚动到可见区域并将其快照为图像块网格。
- 创建一个
NSView or UIView
以正确关系绘制所有图块的子类。
- 调用
printWithPrintInfo
分离视图。
它在 MacOS 10.14+ iOS 13+ 上运行良好
在这两个平台上,所有输出都正确分页(iOS 需要使用包含在相关 GitHub 项目中的 UIPrintPageRenderer),您可以在预览中使用以 PDF 格式打开并将其保存为文件等。
我遇到的唯一缺点是没有使用 Print CSS,这并不重要,因为 Apple 目前对 Print CSS 的支持很少。
所有工作代码都在 GitHub 上:iOS 和 MacOS 的完整工作源
此源已过时,请参阅 Github
标题
//
// WKWebView+UtilityFunctions.h
// Created by Clifford Ribaudo on 12/24/20.
//
#import <WebKit/WebKit.h>
#ifdef _MAC_OS_ // Up to user to determine how they know this
#define IMAGE_OBJ NSImage
#define VIEW_OBJ NSView
#else
#define IMAGE_OBJ UIImage
#define VIEW_OBJ UIView
#endif
@interface TiledImageView : VIEW_OBJ
{
NSArray *_imageTiles;
}
-(void)printWithPrintInfo:(NSPrintInfo *)pi;
-(instancetype)initWithFrame:(CGRect)frame imageTiles:(NSArray<NSArray *> *)imageTiles;
@end
@interface WKWebView (UtilityFunctions)
-(void)HTMLPageMetrics:(void (^)(CGSize htmlDocSize, CGSize visibleSize, NSError *error))completionHandler;
-(void)currentScrollXY:(void (^)(float x, float y, NSError *error))completionHandler;
-(void)scrollHTMLTo:(float)x topY:(float)y completionHandler:(void (^)(NSError *error))completionHandler;
-(void)imageTilesForHTMLPage:(CGSize)pageSize visbleRect:(CGSize)visibleRect imgData:(NSMutableArray<NSArray *> *)tileData completionHandler:(void (^)(NSError *error))completionHandler;
-(void)imageTile:(CGRect)imgRect fromPageOfSize:(CGSize)pageSize inViewOfSize:(CGSize)viewSize completionHandler:(void (^)(IMAGE_OBJ *tileImage, NSError *error))completionHandler;
@end
实施
//
// WKWebView+UtilityFunctions.m
// Created by Clifford Ribaudo on 12/24/20.
//
// Works with MacOS v10.14+ and ??iOS 13+
//
#import "WKWebView+UtilityFunctions.h"
@implementation TiledImageView
-(instancetype)initWithFrame:(CGRect)frame imageTiles:(NSArray<NSArray *> *)imageTiles
{
self = [super initWithFrame:NSRectFromCGRect(frame)];
if(self) {
_imageTiles = imageTiles;
}
return self;
}
-(BOOL)isFlipped {return YES;}
-(void)printWithPrintInfo:(NSPrintInfo *)pi
{
NSPrintOperation *po = [NSPrintOperation printOperationWithView:self];
po.printInfo = pi;
[po runOperation];
}
- (void)drawRect:(NSRect)rect
{
for(NSArray *imgData in _imageTiles)
{
NSRect drawRect = ((NSValue *)imgData[0]).rectValue;
IMAGE_OBJ *img = imgData[1];
[img drawInRect:drawRect];
}
}
@end
@implementation WKWebView (UtilityFunctions)
//
// Returns via Completion Handler:
// htmlDocSize - The size of the entire <HTML> element, visible or not
// visibleSize - The visible dimensions of the page, essentially WKWebView bounds minus HTML scroll bar dimensions
//
-(void)HTMLPageMetrics:(void (^)(CGSize htmlDocSize, CGSize visibleSize, NSError *error))completionHandler
{
//
// Anonymous Function - gets Size of entire HTML element and visible size.
// Result String = Full X, Full Y, Visible X, Visible Y
//
NSString *jsGetPageMetrics = @"(function(){return document.documentElement.scrollWidth + ',' + document.documentElement.scrollHeight + ',' + document.documentElement.clientWidth + ',' +document.documentElement.clientHeight;})();";
// Execute JS in WKWebView
[self evaluateJavaScript:jsGetPageMetrics completionHandler:^(id result, NSError *error)
{
CGSize htmlSize = CGSizeMake(0, 0);
CGSize visibleSize = CGSizeMake(0, 0);
if(!error && result)
{
NSArray<NSString *> *data = [[NSString stringWithFormat:@"%@", result] componentsSeparatedByString:@","];
htmlSize = CGSizeMake([data[0] floatValue], [data[1] floatValue]);
visibleSize = CGSizeMake([data[2] floatValue], [data[3] floatValue]);
}
else
NSLog(@"JS error getting page metrics: %@", error.description);
completionHandler(htmlSize, visibleSize, error);
}];
}
//
// Get <HTML> element current scroll position (x,y) and return to completeion handler:
// x = document.documentElement.scrollLeft
// y = document.documentElement.scrollTop
//
-(void)currentScrollXY:(void (^)(float X, float Y, NSError *error))completionHandler
{
NSString *jsGetPageMetrics = @"(function(){return document.documentElement.scrollLeft + ',' + document.documentElement.scrollTop;})();";
// Execute JS in WKWebView
[self evaluateJavaScript:jsGetPageMetrics completionHandler:^(id result, NSError *error) {
if(!error && result)
{
NSArray<NSString *> *data = [[NSString stringWithFormat:@"%@", result] componentsSeparatedByString:@","];
completionHandler([data[0] floatValue], [data[1] floatValue], error);
}
else {
NSLog(@"JS error getting page metrics: %@", error.localizedDescription);
completionHandler(0, 0, error);
}
}];
}
//
// Scroll the current HTML page to x, y using scrollTo(x,y) on the <HTML> element
// Optional Completion Handler to do something when scroll finished
//
-(void)scrollHTMLTo:(float)x topY:(float)y completionHandler:(void (^)(NSError *error))completionHandler
{
NSString *js = [NSString stringWithFormat:@"document.documentElement.scrollTo(%0.f, %0.f);", x, y];
// Execute JS in WKWebView
[self evaluateJavaScript:js completionHandler:^(id result, NSError *error)
{
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, .25 * NSEC_PER_SEC);
dispatch_after(delay, dispatch_get_main_queue(), ^{
if(completionHandler) completionHandler(error);
});
if(error) NSLog(@"JS error scrollTo %@", error.localizedDescription);
}];
}
//
// Called Recursively until tiles are obtained for the entire pageRect.
// Tiles are the size of visibleRect (WKWebView.bounts) but can be smaller.
// tileData - Array of arrays holding CGRect & Img.
//
-(void)imageTilesForHTMLPage:(CGSize)pageSize visbleRect:(CGSize)visibleSize imgData:(NSMutableArray<NSArray *> *)tileData completionHandler:(void (^)(NSError *error))completionHandler
{
__block CGRect currentRect; // In coordinates of pageSize (full).
if(tileData.count == 0) { // No image tiles yet. Start at top left of html page for visible WKWebView bounds
currentRect.origin.x = currentRect.origin.y = 0.0;
currentRect.size = visibleSize;
}
else {
NSArray *lastTile = [tileData lastObject]; // Calculate what the next tile rect is or call handler if done.
CGRect lastTileRect;
#ifdef _MAC_OS_
lastTileRect = ((NSValue *)lastTile[0]).rectValue;
#else
lastTileRect = ((NSValue *)lastTile[0]).CGRectValue;
#endif
// Check if anything more to get to right of last tile
if((lastTileRect.origin.x + lastTileRect.size.width) < pageSize.width)
{
currentRect.origin.x = lastTileRect.origin.x + lastTileRect.size.width + 1; // Next x to right of last tile
currentRect.origin.y = lastTileRect.origin.y; // Works on all rows
currentRect.size.height = lastTileRect.size.height;
currentRect.size.width = pageSize.width - currentRect.origin.x; // Get width of next tile to right of last
if(currentRect.size.width > visibleSize.width) // If more tiles to right use visible width
currentRect.size.width = visibleSize.width;
}
else if((lastTileRect.origin.y + lastTileRect.size.height) < pageSize.height) // New Row
{
currentRect.origin.x = 0; // Reset x back to left side of hmtl
currentRect.size.width = visibleSize.width; // Reset width back to view width
currentRect.origin.y = lastTileRect.origin.y + lastTileRect.size.height + 1; // Get y below last row
currentRect.size.height = pageSize.height - currentRect.origin.y;
if(currentRect.size.height > visibleSize.height) // If more rows below use row height
currentRect.size.height = visibleSize.height;
}
else {
completionHandler(nil);
return;
}
}
[self imageTile:currentRect fromPageOfSize:pageSize inViewOfSize:visibleSize completionHandler:^(NSImage *tileImage, NSError *error)
{
if(error || !tileImage) {
NSLog(@"Error getting image tiles %@", error.description);
completionHandler(error);
return;
}
#ifdef _MAC_OS_
[tileData addObject:@[[NSValue valueWithRect:NSRectFromCGRect(currentRect)], tileImage]];
#else
[tileData addObject:@[[NSValue valueWithCGRect:currentRect], tileImage]];
#endif
[self imageTilesForHTMLPage:(CGSize)pageSize visbleRect:(CGSize)visibleSize imgData:(NSMutableArray<NSArray *> *)tileData completionHandler:completionHandler];
}];
}
//
// ImgRect = location of rect in full page size. Has to be translated into what is visible and where.
// pageSize = Full size of HTML page, visible or not.
// viewSize = essentially the wkwebview.bounds.size - HTML scroll bars.
//
-(void)imageTile:(CGRect)imgRect fromPageOfSize:(CGSize)pageSize inViewOfSize:(CGSize)viewSize completionHandler:(void (^)(IMAGE_OBJ *tileImage, NSError *error))completionHandler
{
float x = imgRect.origin.x; // Always do this to make the desired rect visible in the rect of viewSize
float y = imgRect.origin.y;
CGRect rectToGetFromView;
rectToGetFromView.origin.x = 0;
rectToGetFromView.origin.y = 0;
rectToGetFromView.size = imgRect.size;
// If img is smaller than the viewport, determine where it is after scroll
if(imgRect.size.width < viewSize.width)
rectToGetFromView.origin.x = viewSize.width - imgRect.size.width;
if(imgRect.size.height < viewSize.height)
rectToGetFromView.origin.y = viewSize.height - imgRect.size.height;
[self scrollHTMLTo:x topY:y completionHandler:^(NSError *error)
{
if(!error) {
WKSnapshotConfiguration *sc = [WKSnapshotConfiguration new];
sc.rect = rectToGetFromView;
[self takeSnapshotWithConfiguration:sc completionHandler:^(IMAGE_OBJ *img, NSError *error)
{
if(error) NSLog(@"Error snapshotting image tile: %@", error.description);
completionHandler(img, error);
}];
}
else {
NSLog(@"Error scrolling for next image tile %@", error.description);
completionHandler(nil, error);
}
}];
}
@end
用法
在任何处理打印的内容中使用类别,WKWebView
如下所示:
-(void)print:(id)sender
{
// Set this as per your needs
NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo];
pInfo.verticallyCentered = YES;
pInfo.horizontallyCentered = NO;
pInfo.horizontalPagination = NSAutoPagination;
pInfo.verticalPagination = NSAutoPagination;
pInfo.orientation = NSPaperOrientationLandscape;
pInfo.bottomMargin = 30;
pInfo.topMargin = 30;
pInfo.leftMargin = 30;
pInfo.rightMargin = 30;
pInfo.scalingFactor = .60;
[_webView HTMLPageMetrics:^(CGSize htmlSize, CGSize visibleSize, NSError *error)
{
self->_imgTileData = [NSMutableArray new];
[self->_webView imageTilesForHTMLPage:htmlSize visbleRect:visibleSize imgData:self->_imgTileData completionHandler:^(NSError *error) {
if(!error) {
TiledImageView *tiv = [[TiledImageView alloc] initWithFrame:CGRectMake(0,0,htmlSize.width,htmlSize.height) imageTiles:self->_imgTileData];
[tiv printWithPrintInfo:pInfo];
}
}];
}
}
这是作为 Github Gist 的代码:上面的代码
从WKWebView
下面的内容开始,也向右滚动:
一个人得到这个带有适当分页的打印对话框: