iOS将HTML页面转换成PDF文件保存到本地并分享传输文件

第一次修改: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的代码也做了同步更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,728评论 1 92
  • 1、设置UILabel行间距 NSMutableAttributedString* attrString = [[...
    FF_911阅读 1,353评论 0 3
  • 老人是智慧的,无论他们上过多少学,他们就是一本活教科书,总会显现出来各种各样的生活技巧,老人不是累赘,...
    安qian阅读 334评论 0 0
  • 今天霜降 这场秋雨淅淅沥沥滴滴答答缠绵了三四天了,今天迎来了秋天最后一个节气——霜降。寒意侵衣薄,白露凝为霜,七里...
    阳光坐怀阅读 484评论 0 2
  • 从出发前的担心,紧张又有点小激动\(≧≦)/,经过了一夜的颠簸,拖着我随身将近30多公斤的行李到达了提前预定的青旅...
    鉄栅栏阅读 168评论 0 0