第一次修改:2017.05.26
修改内容:之前的方法在转化带有图片的html页面时会出现图片缺失问题,由于之前我需要转换的html不包含图片,所以没有发现这一问题,现在增加一个直接将webView转化为PDF的方法,在下面进行详述。
前言:最近项目有个新的需求:用户可以将当前HTML页面内容保存到本地,并可将此内容进行分享。为此我想了两种办法:
1、将HTML页面截屏拼接为长图进行保存;
2、将HTML页面转换为PDF文件进行保存;
相比之下,显然第一种在操作性和实用性上都不太合适,因此我选择第二种方法进行实现。
Github:看这里
简单整理下思路,我们可以分以下几步进行处理:
1、获取HTML文件
2、对HTML富文本进行处理
3、将处理后的富文本进行PDF文件保存
4、对PDF文件进行分享
1、获取HTML文件
//获取本地HTML内容并进行加载展示
NSString *readmePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
_htmlStr = [NSString stringWithContentsOfFile:readmePath encoding:NSUTF8StringEncoding error:nil];
[webView loadHTMLString:_htmlStr baseURL:nil];
2、对HTML富文本进行处理
这里我们通过集成DTCoreText对富文本进行处理:
先简单介绍一下CoreText,CoreText是iOS/OSX里的文字渲染引擎,在iOS/OSX中看到的所有文字在底层都是由CoreText去渲染。
DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需要显示富文本的场景下代替低性能的UIWebView。具体可参见这篇文章:iOS富文本组件的实现—DTCoreText源码解析。
然而不得不说一下DTCoreText这个开源框架的集成还是很坑爹的,一般的第三方框架直接down下来拖到项目中就能用,但是这个框架down下来之后缺少文件,从cocoapods集成运行时又会出现crash。我试着集成了几次都以失败告终,最后上网查找了一下集成方法,使用Framwork的方式导入;有兴趣的也可以看看:官方提供的导入方法。
集成DTCoreText:
1、将两个依赖库导入项目中
2、按图示步骤进行操作:选中target的BuildPhases-->选中左侧加号NewCopy Files Phases-->将两个依赖库添加进去,选择Destination为Frameworks
新建OCPDFGenerator类对HTML文本进行处理:
#import@interface OCPDFGenerator : NSObject
+(NSString *)generatePDFFromAttributedString:(NSAttributedString *)str;
+(NSString *)generatePDFFromHTMLString:(NSString *)str;
@end
将HTML文本内容转换为富文本:
+(NSString *)generatePDFFromHTMLString:(NSString *)html {
NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
// Create attributed string from HTML
CGSize maxImageSize = CGSizeMake(500, 500);
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithFloat:1.0], NSTextSizeMultiplierDocumentOption, [NSValue valueWithCGSize:maxImageSize], DTMaxImageSize,
@"Arial", DTDefaultFontFamily, @"blue", DTDefaultLinkColor, nil];
NSAttributedString *str = [[NSAttributedString alloc]initWithHTMLData:data options:options documentAttributes:NULL];
return [self generatePDFFromAttributedString:str];
}
3、将处理后的富文本进行PDF文件保存
创建目标为指定可变数据对象的基于PDF的图形上下文,并进行存储:
+(NSString *)generatePDFFromAttributedString:(NSAttributedString *)str {
NSString *fileName = @"testFile.pdf";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *newFilePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:fileName];
int fontSize = 12;
NSString *font = @"Verdana";
UIColor *color = [UIColor blackColor];
int DOC_WIDTH = 612;
int DOC_HEIGHT = 792;
int LEFT_MARGIN = 50;
int RIGHT_MARGIN = 50;
int TOP_MARGIN = 50;
int BOTTOM_MARGIN = 50;
int CURRENT_TOP_MARGIN = TOP_MARGIN;
//You can make the first page have a different top margin to place headers, etc.
int FIRST_PAGE_TOP_MARGIN = TOP_MARGIN;
CGRect a4Page = CGRectMake(0, 0, DOC_WIDTH, DOC_HEIGHT);
NSDictionary *fileMetaData = [[NSDictionary alloc] init];
if (!UIGraphicsBeginPDFContextToFile(newFilePath, a4Page, fileMetaData )) {
NSLog(@"error creating PDF context");
return nil;
}
BOOL done = NO;
CGContextRef context = UIGraphicsGetCurrentContext();
CFRange currentRange = CFRangeMake(0, 0);
CGContextSetTextDrawingMode (context, kCGTextFill);
CGContextSelectFont (context, [font cStringUsingEncoding:NSUTF8StringEncoding], fontSize, kCGEncodingMacRoman);
CGContextSetFillColorWithColor(context, [color CGColor]);
// Initialize an attributed string.
CFAttributedStringRef attrString = (__bridge CFAttributedStringRef)str;
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
int pageCount = 1;
do {
UIGraphicsBeginPDFPage();
CGMutablePathRef path = CGPathCreateMutable();
if(pageCount == 1) {
CURRENT_TOP_MARGIN = FIRST_PAGE_TOP_MARGIN;
} else {
CURRENT_TOP_MARGIN = TOP_MARGIN;
}
CGRect bounds = CGRectMake(LEFT_MARGIN,
CURRENT_TOP_MARGIN,
DOC_WIDTH - RIGHT_MARGIN - LEFT_MARGIN,
DOC_HEIGHT - CURRENT_TOP_MARGIN - BOTTOM_MARGIN);
CGPathAddRect(path, NULL, bounds);
// Create the frame and draw it into the graphics context
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, currentRange, path, NULL);
if(frame) {
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, bounds.origin.y);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -(bounds.origin.y + bounds.size.height));
CTFrameDraw(frame, context);
CGContextRestoreGState(context);
// Update the current range based on what was drawn.
currentRange = CTFrameGetVisibleStringRange(frame);
currentRange.location += currentRange.length;
currentRange.length = 0;
}
// If we're at the end of the text, exit the loop.
if (currentRange.location == CFAttributedStringGetLength((CFAttributedStringRef)attrString))
done = YES;
pageCount++;
} while(!done);
UIGraphicsEndPDFContext();
return newFilePath;
}
4、对PDF文件进行分享
UIDocumentInteractionController是从iOS 3.2的SDK开始支持的,它是直接继承的NSObject,UIDocumentInteractionController主要给我们提供了三种用途:
展示一个可以操作我们分享的文档类型的第三方App列表,包括官方处理应用,第三方应用,AirDrop等。
在第一条展示列表的基础上添加额外的操作,比如复制,打印,预览,保存等。
结合Quick Look框架直接展示文档内容。
代码实现:
- (void)sharePdf{
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/testFile.pdf"];
UIDocumentInteractionController *ctrl = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
ctrl.delegate = self;
ctrl.UTI = @"com.adobe.pdf";
[ctrl presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];
}
注:这里运行之后点击iBooks进行分享,会发现程序crash,查看crash日志发现:猜测是UIDocumentInteractionController提前释放内存导致,于是我们将UIDocumentInteractionController对象声明为strong类型的实例对象,防止其内存提前释放即可。
@property (nonatomic, strong) UIDocumentInteractionController *documentInterCtrl;
2017.05.26修改内容:
上面的方法由于是直接对富文本进行操作,所以忽略了处理图片这一问题。上网研究了一下,发现可以用UIPrintPageRenderer进行页面绘制并保存,这一方法更简单,代码也更简洁。
具体实现:
1. 创建一个webView的类别用于转换文件,并添加内容转换的接口@interface UIWebView (ConverToData)
@interface UIWebView (ConverToData)
- (NSData *)converToPDF;
2. 使用UIPrintPageRenderer对当前webView进行绘制
- (NSData *)converToPDF{
UIViewPrintFormatter *fmt = [self viewPrintFormatter];
UIPrintPageRenderer *render = [[UIPrintPageRenderer alloc] init];
[render addPrintFormatter:fmt startingAtPageAtIndex:0];
CGRect page;
page.origin.x=0;
page.origin.y=0;
page.size.width=600;
page.size.height=768;
CGRect printable=CGRectInset( page, 50, 50 );
[render setValue:[NSValue valueWithCGRect:page] forKey:@"paperRect"];
[render setValue:[NSValue valueWithCGRect:printable] forKey:@"printableRect"];
NSMutableData * pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData( pdfData, CGRectZero, nil );
for (NSInteger i=0; i < [render numberOfPages]; i++)
{
UIGraphicsBeginPDFPage();
CGRect bounds = UIGraphicsGetPDFContextBounds();
[render drawPageAtIndex:i inRect:bounds];
}
UIGraphicsEndPDFContext();
return pdfData;
}
Github的代码也做了同步更新。