iOS端加载PDF文件实践

最近项目中需要加载PDF文件, 遇到一些小问题, 记录一下

常见加载PDF的方式

iOS上面加载PDF一般分为两种方式, 一种是使用web view, 加载本地PDF文件或者网络PDF数据; 另一种就是将PDF文件重新绘制, 使用自定义view进行显示. 两种方式各有优劣: 第一种方式简单, 粗暴, 没有编辑的时候使用这种方式进行简单PDF浏览, 能达到比较好的效果, 缺点是不能进行编辑, 显示的信息比较简略, 而且web view本身是有内存问题的; 自定义view的灵活性比较大, 一般的阅读软件都是使用这种方式, 可以分页显示, 进行编辑等操作, 缺点是与第一种方式相比下, 需要写较多的代码以及考虑性能优化的问题.

使用简单示例

  1. 第一种方式示例代码:

    1
    2
    3
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"demo" withExtension:@"pdf"];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [webView loadRequest:request];

    由于有的PDF是有编码的, 默认使用的是UTF-8

    1
    2
    NSData * data = [NSData dataWithContentsOfFile:url];
    [webView loadData:data MIMEType:@"application/pdf" textEncodingName:@"UTF-8" baseURL:url];
  2. 第二种方式示例代码:
    这里有一篇博客,示例代码
    创建PDF密码
    PDF横竖屏切换
    感谢念茜大神的博客.
    简单来说,就是首先拿到文件,转为data,然后通过系统函数转为CGContextRef对象,然后CGPDFDocumentGetNumberOfPages()可以获取到PDF的页数,然后通过页数,CG_EXTERN CGPDFPageRef __nullable CGPDFDocumentGetPage( CGPDFDocumentRef __nullable document, size_t pageNumber) CG_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0)可以获取到指定页的PDF,然后进行PDF绘制就好了.
    自定义view核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)drawRect:(CGRect)rect {
// 坐标系转换
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
long pageSum = CGPDFDocumentGetNumberOfPages(pdfDocument);
pageNO = pageNO == 0 ? 1 : pageNo;
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, pageNO);
CGContextSaveGState(context);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, self.bounds, 0, true);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
}

第二种方式的新问题

上面就是两种常见PDF的显示方式, 但是第二种如果这么处理, 会带来一个问题, 就是如果进行放大处理, PDF会很模糊, 看了一下多看的PDF显示, 明显不是使用的第一种方式, 但是放大之后依然很清晰, 怎么处理的呢? 答案是使用CATiledLayer替换自定义view的layer.
移动设备的图片处理能力终究是有限的, 如果需要显示一张分辨率非常高的图片, 直接用image view加载可能会爆内存, 并且通常一张巨大的图片, 比如地图, 我们经常查看的是一部分而不是整体, 为了高效绘图, 苹果提供了CATiledLayer类, 这个类可以很好地解决我们的问题. 详细请参考苹果官方文档或者《iOS核心动画高级技巧》的专用图层CATiledLayer章节.
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
+ (Class)layerClass {
return [DHCATiledLayer class];
}
//初始化方法
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
DHCATiledLayer* layer = (DHCATiledLayer *)[self layer];
layer.levelsOfDetail = 10;
layer.levelsOfDetailBias = 10;
layer.tileSize = CGSizeMake(4086.0, 4086.0);
}

这个类有隐式动画,继承并重写方法,动画时长为0即可:

1
2
3
+(CFTimeInterval)fadeDuration {
return 0.0;
}

这篇博客里面写了如何去除UIView动画以及layer动画.