0
最近項(xiàng)目中需要給客戶看一些比較私密的文件雷滚,為了防止盜取和篡改需曾,需要用 PDF 格式,為 app 添加了一個(gè)簡單的 PDF 閱讀模塊祈远。
打開 PDF
iOS 打開 PDF 是非常方便的事情呆万,系統(tǒng)原生支持。
- (BOOL)openPDFFile:(NSString*)filePath{
NSURL* pdfURL = [NSURL fileURLWithPath:filePath];
CFURLRef cfpdfURL = CFURLCreateWithFileSystemPath(CFAllocatorGetDefault(), CFStringCreateWithCString(CFAllocatorGetDefault(), pdfURL.path.UTF8String, kCFStringEncodingUTF8), kCFURLPOSIXPathStyle, NO);
_pdfDocument = CGPDFDocumentCreateWithURL(cfpdfURL);
CFRelease(cfpdfURL);
if (_pdfDocument == nil) {
return NO;
}
_totalPageNum = CGPDFDocumentGetNumberOfPages(_pdfDocument);
return YES;
}
CFStringCreateWithCString(CFAllocatorGetDefault(), pdfURL.path.UTF8String, kCFStringEncodingUTF8)
CGPDFDocumentCreateWithURL這里的 path车份,可以使用 NSURL 的fileURLWithPath:來獲取谋减,并用CFStringCreateWithCString轉(zhuǎn)換。
_pdfDocument 是 CGPDFDocumentRef 類型的扫沼,在打開 PDF 文件以后逃顶,一直持有 PDF 文件的引用。
_totalPageNum = CGPDFDocumentGetNumberOfPages(_pdfDocument);
使用這個(gè)方法獲取 PDF 總頁數(shù)充甚,供翻頁的時(shí)候使用。
- (PDFContentViewController* _Nullable)showPage:(size_t)pageNum{
if (_totalPageNum > 0 && pageNum <= _totalPageNum && pageNum > 0) {
if (_currentPage != nil) {
_currentPage = nil;
}
_currentPage = CGPDFDocumentGetPage(_pdfDocument, pageNum);
PDFContentViewController* pdfContentViewController = [[PDFContentViewController alloc]init];
[pdfContentViewController showPdfPage:_currentPage];
pdfContentViewController.pageNumber = pageNum;
return pdfContentViewController;
}else{
return nil;
}
}
這里使用 CGPDFDocumentGetPage 來獲取 PDF 文件中某一頁的引用霸褒。
第一個(gè)參數(shù)是對 pdf 文件的引用伴找,第二個(gè)參數(shù)可以直接取到某一頁。這里很奇快的是废菱,page 的下標(biāo)是從1開始的技矮,向CGPDFDocumentGetPage傳入0會(huì)直接報(bào)錯(cuò)。
PDFContentViewController是一個(gè)顯示 PDF 頁面的類殊轴,每個(gè) PDF 頁面創(chuàng)建一個(gè)類衰倦。剛好給 UIPageViewController 做翻頁效果使用。UIPageViewController 用起來很坑旁理,這個(gè)以后再說樊零。
PDF文件顯示
繪制 pdf 文件
繪制 pdf 文件跟平時(shí)繪制圖片差不多
CGSize pdfPageOriginalPageSize = CGPDFPageGetBoxRect(_pdfPage, PDFContentDefaultPDFBox).size;
CGSize imageSize = CGSizeMake(self.pdfContentView.bounds.size.width, self.pdfContentView.bounds.size.height);
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.contentBackground.CGColor);
CGContextSetStrokeColorWithColor(context, self.contentBackground.CGColor);
CGContextFillRect(context,CGRectMake(0, 0, imageSize.width, imageSize.height));
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, imageSize.height);
CGContextScaleCTM(context, 1, -1);
CGContextScaleCTM(context, imageSize.width/(pdfPageOriginalPageSize.width > 0?pdfPageOriginalPageSize.width:imageSize.width), imageSize.height/(pdfPageOriginalPageSize.height > 0?pdfPageOriginalPageSize.height:imageSize.height));
CGContextDrawPDFPage(context, _pdfPage);
CGContextRestoreGState(context);
UIImage* thumbImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.pdfContentView.layer.contents = (__bridge id _Nullable)(thumbImg.CGImage);
關(guān)鍵是這里
CGContextDrawPDFPage(context, _pdfPage);
這里繪制的時(shí)候,繪制 context 的 CTM 是相對于 pdf 文件來變換的(跟繪制 Image 到 CGContext 上的變換規(guī)則是一樣的,想象成繪制圖片就好)
如果不變換 CTM 就會(huì)繪制一個(gè)倒立的圖像驻襟。并且夺艰,上面的代碼中,pdf 頁面的大小遠(yuǎn)遠(yuǎn)大于 UIGraphicsBeginImageContext(imageSize) 生成的 Image沉衣,于是就看到一個(gè)倒立的郁副,只繪制了一部分的 pdf 文件。
這段代碼是繪制了一個(gè) Thumbnail Image豌习,所以就沒有繪制高清的圖片存谎,如果需要繪制高清的圖片,例如在 7s plus 上顯示的圖片肥隆,至少要繪制一個(gè) 3x 于屏幕分辨率的圖片既荚,縮小后顯示出來。
CGContextScaleCTM(context, imageSize.width/(pdfPageOriginalPageSize.width > 0?pdfPageOriginalPageSize.width:imageSize.width), imageSize.height/(pdfPageOriginalPageSize.height > 0?pdfPageOriginalPageSize.height:imageSize.height));
繪制的關(guān)鍵在這里巷屿,將整個(gè) pdf 頁面固以,縮放到與即將生成的 image 同樣大小,就可以把整個(gè)頁面都繪制到 image 上面了嘱巾。
在計(jì)算 scale 的時(shí)候憨琳,也可以用
CGPDFPageGetDrawingTransform(_PDFPageRef, kCGPDFCropBox, self.bounds, 0, true)
系統(tǒng)會(huì)根據(jù)第二個(gè)參數(shù)傳入的值,將 pdf 裁剪之后放入第三個(gè)參數(shù)傳入的 Rect 里面旬昭。
CGSize pdfPageOriginalPageSize = CGPDFPageGetBoxRect(_pdfPage, PDFContentDefaultPDFBox)
這個(gè)需要說明一下篙螟,每個(gè) pdf 頁面都有一個(gè)原始大小。雖然 PDF 格式是矢量圖问拘,但是遍略,其中的圖片等內(nèi)容不是矢量的,這個(gè)原始大小也就比較容易理解了骤坐。第二個(gè)參數(shù) CGPDFBox绪杏。系統(tǒng)會(huì)根據(jù)傳入的值來返回一個(gè)裁剪后的 pdf 頁面大小
typedef CF_ENUM (int32_t, CGPDFBox) {
kCGPDFMediaBox = 0,
kCGPDFCropBox = 1,
kCGPDFBleedBox = 2,
kCGPDFTrimBox = 3,
kCGPDFArtBox = 4
};
具體的裁剪規(guī)則,還沒有研究清楚纽绍。
此外蕾久,需要知道的是,PDF 里面的每個(gè)元素都是可以讀取到的拌夏,iOS 也給我們提供了相應(yīng)的方法僧著。
如果有打開 pdf 中的 url 之類的需求,可以研究一下這方面的內(nèi)容障簿。
在回到顯示部分盹愚,實(shí)際瀏覽時(shí),如果存在放大縮小滑動(dòng)等動(dòng)作站故,就必須重新繪圖皆怕。用 UIScrollview 的 contentOffset 來計(jì)算當(dāng)前正在顯示的區(qū)域,并繪制到屏幕上。
但是會(huì)產(chǎn)生幾個(gè)問題:1.如果繪制與 UIScrollView 的 ContentSize 同樣大小的圖片端逼,那么朗兵,當(dāng) Scale 非常大的時(shí)候,繪制的圖片也非常大顶滩,最終會(huì)內(nèi)存占用過高而崩潰余掖。
2.手動(dòng)計(jì)算正在顯示的區(qū)域比較困難。
好在 CATiledLayer可以幫我們解決這個(gè)難題礁鲁,但是 CATiledLayer 也有一些坑盐欺。
使用 CATiledLayer 顯示 PDF
上面的繪圖代碼是顯示縮略圖的繪制代碼,由于CATiledLayer 繪制圖片是異步的仅醇,在繪制過程中冗美,會(huì)存在一塊一塊閃爍的情況,在 CATiledLayer 下面放一個(gè)縮略圖析二,效果比較好粉洼。
CATiledLayer是 iOS 提供的一個(gè)專門顯示大圖片的類,它可以分塊加載叶摄,異步加載属韧。并且有自己的內(nèi)存管理機(jī)制,可以把屏幕外的部分及時(shí)移除蛤吓。
在 UIScrollview 上面使用 CATiledLayer 時(shí)需要設(shè)置
levelsOfDetail 和 levelsOfDetailBias宵喂。
levelsOfDetail = 3指的是,UIScrollview 縮放倍數(shù)(scale)在1/2 1/4 1/8時(shí)使用同樣的 scale 重繪屏幕会傲」兀縮小時(shí)節(jié)省內(nèi)存。
levelsOfDetailBias = 3 指的是UIScrollview 縮放倍數(shù)(scale)在2 4 8時(shí)使用同樣的 scale 重繪屏幕淌山。保持清晰度裸燎。
簡直就是為展示 PDF 文件生的。
只要設(shè)置CATiledLayer的 delegate泼疑,并且在
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
里面繪制就可以了德绿,但是不能手動(dòng)設(shè)置CATiledLayer的 content。
這里就是 delegate 的繪制方法
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context{
PDFContentViewController* strongSelf = self;
double screenScale = [UIScreen mainScreen].scale;
CGSize pdfPageOriginalPageSize = CGPDFPageGetBoxRect(strongSelf->_pdfPage, PDFContentDefaultPDFBox).size;
CGSize imageSize = CGSizeMake(layer.bounds.size.width, layer.bounds.size.height);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context,CGContextGetClipBoundingBox(context));
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, layer.bounds.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextScaleCTM(context, imageSize.width/(pdfPageOriginalPageSize.width > 0?pdfPageOriginalPageSize.width:imageSize.width), imageSize.height/(pdfPageOriginalPageSize.height > 0?pdfPageOriginalPageSize.height:imageSize.height));
CGContextDrawPDFPage(context, strongSelf->_pdfPage);
CGContextRestoreGState(context);
}
需要注意的是:context在傳入這個(gè)方法時(shí)王浴,已經(jīng)做過 CTM 變換了,它的 Clip 和 scale 都可以發(fā)生了改變梅猿。
獲取 scale 使用
CGContextGetCTM(context).a
獲取 clip 使用
CGContextGetClipBoundingBox(context)
通常情況下氓辣,這個(gè) context 描繪的就是當(dāng)前需要繪制的塊,不要改變 scale 和 clip袱蚓〕ィ可以忽略這個(gè)變換,直接在這個(gè)基礎(chǔ)上繼續(xù)變換 context 即可,就好像在一個(gè)普通的 layer 繪制全屏的圖像一樣体斩。
但是這個(gè)地方是多線程的梭稚,如果處理不好,可能會(huì)引用的已經(jīng)釋放的 self 變量絮吵,導(dǎo)致崩潰弧烤。這里使用 weak-strong self 的方式進(jìn)行處理。
這本書非常好蹬敲,看過 PDF 之后已入正收藏暇昂。
內(nèi)存占用也比較小。
現(xiàn)在已經(jīng)完成了 PDF 文件的打開和繪制一個(gè)頁面伴嗡,下一步就要進(jìn)行縮放和翻頁等操作了
http://www.cocoachina.com/bbs/read.php?tid-48894-keyword-CATiledLayer.html
http://stackoverflow.com/questions/2295151/catiledlayer-drawing-crash
https://developer.apple.com/library/content//qa/qa1637/_index.html