開發(fā)環(huán)境:XCode7.3,Deployment Target:8.0
話說疙教,公司項(xiàng)目需要顯示PDF文件棺聊,于是遍尋了網(wǎng)絡(luò),發(fā)現(xiàn)的方法以下幾種:
1.使用UIWebView加載贞谓,沒啥說的限佩,根據(jù)文件路徑,網(wǎng)絡(luò)或者本地皆可经宏,創(chuàng)建一個(gè)NSURLRequest犀暑,然后用webView加載就可以了,但僅僅能顯示文件烁兰,很low耐亏;
2.使用UIDocumentInteractionController或QLPreviewController進(jìn)行預(yù)覽——依然可以很方便的查看PDF文檔,還有些系統(tǒng)自帶的方法和功能沪斟,但是這依舊不是我要的广辰。
3.使用第三方——Reader(vfr),功能很強(qiáng)大主之,可配置性高择吊,遠(yuǎn)遠(yuǎn)超出了我需要的范疇,不能更完美了槽奕。然而几睛,就在我高高興興的準(zhǔn)備把它加入項(xiàng)目的時(shí)候,才發(fā)現(xiàn)這貨不能加載網(wǎng)絡(luò)上的PDF資源-_-|||于是我嘗試把網(wǎng)絡(luò)文件下載到了本地粤攒,寫入了沙盒cache目錄所森,然后還是不能加載……使勁的找了一下,似乎應(yīng)該寫到沙盒的doc目錄夯接,于是又把文件下載到了doc目錄焕济,然后依舊不能加載%>_<%好吧,只好自己動(dòng)手了盔几。(有兄弟姐妹成功使用過晴弃,萬望指教)
4.自己寫,當(dāng)然這也是這篇文章要寫的,功能包含:獲取網(wǎng)絡(luò)/本地PDF文件并顯示上鞠,可以實(shí)現(xiàn)放大或縮小顯示的效果际邻。
以下上貨:
項(xiàng)目思路(這很重要,只抄代碼自然是能實(shí)現(xiàn)功能的旗国,但是……):
第一步枯怖,自然是獲取pdf資源。Bundle中的資源就不說了能曾,如果你的PDF文件都是存在于Bundle中的度硝,那么使用Reader(vfr)是最好的了,我們這里只說網(wǎng)絡(luò)和本地資源(好吧寿冕,本地資源和網(wǎng)絡(luò)資源的區(qū)別很簡(jiǎn)單的說就在于他們的url地址不同蕊程,所以,我們只說網(wǎng)絡(luò)資源吧驼唱,本地資源藻茂,對(duì)應(yīng)替換url地址就好了)
下方的方法會(huì)通過一個(gè)url字符串,返回一個(gè)CGPDFDocumentRef格式的數(shù)據(jù)玫恳,你可能看到這不是一個(gè)OC方法的寫法辨赐,是的,它不是京办。
CGPDFDocumentRef ? test(NSString*urlString) {
NSURL*url = [NSURLURLWithString:urlString];//將傳入的字符串轉(zhuǎn)化為一個(gè)NSURL地址
CFURLRefrefURL = (__bridge_retainedCFURLRef)url;//將的到的NSURL轉(zhuǎn)化為CFURLRefrefURL備用
CGPDFDocumentRefdocument =CGPDFDocumentCreateWithURL(refURL);//通過CFURLRefrefURL獲取文件內(nèi)容
CFRelease(refURL);//過河拆橋掀序,釋放使用完畢的CFURLRefrefURL,這個(gè)東西并不接受自動(dòng)內(nèi)存管理惭婿,所以要手動(dòng)釋放
if(document) {
return ?document;//返回獲取到的數(shù)據(jù)
}else{
return ? NULL; //如果沒獲取到數(shù)據(jù)不恭,則返回NULL,當(dāng)然财饥,你可以在這里添加一些打印日志换吧,方便你發(fā)現(xiàn)問題
}
}
第二步,將獲取到的數(shù)據(jù)顯示出來钥星,好吧沾瓦,這里就不是一句話能搞定的了,我們需要細(xì)說谦炒。
首先贯莺,我們獲取到的PDF資源十有八九不僅僅是一頁,而是很多頁编饺,所以肯定不可能在同一個(gè)視圖上顯示乖篷。那么我們就需要單獨(dú)的獲取到PDF資源數(shù)據(jù)中某一頁的數(shù)據(jù)响驴,別慌透且,系統(tǒng)有專門的函數(shù);然后我們大概還需要知道這個(gè)PDF資源一共有多少頁,別慌秽誊,這個(gè)系統(tǒng)也有專門的函數(shù)鲸沮,我們會(huì)在用到的時(shí)候說明。
其次锅论,獲取到某一頁的數(shù)據(jù)后讼溺,我們還要把它展示到一個(gè)view上面,最后很多個(gè)view 的集合就是我們需要展示的所有東西了最易。
所以怒坯,我們需要自定義一個(gè)view,然后傳入我們通過上面方法已經(jīng)獲取到的CGPDFDocumentRef數(shù)據(jù)和需要顯示的頁數(shù)藻懒,讓這個(gè)view來展示對(duì)應(yīng)頁數(shù)的PDF文件內(nèi)容剔猿。(為什么需要一個(gè)view,因?yàn)檫@里會(huì)用到Quartz2D繪圖嬉荆,需要重寫view的- (void)drawRect:(CGRect)rect方法來實(shí)現(xiàn)PDF文件內(nèi)容的繪制)
那么下一步自然是新建一個(gè)view归敬,繼承自UIView,這里我取名為RiderPDFView鄙早,以下為.h文件內(nèi)容:
```
#import <UIKit/UIKit.h>
@interface ?RiderPDFView :UIView
//寫一個(gè)方法汪茧,通過Frame、已經(jīng)獲取到的CGPDFDocumentRef文件和需要顯示的PDF文件的頁碼限番,來創(chuàng)建一個(gè)顯示PDF文件內(nèi)容的視圖
- (instancetype)initWithFrame:(CGRect)frame documentRef:(CGPDFDocumentRef)docRef andPageNum:(int)page;
@end
```
然后是自定義的視圖中.m文件的內(nèi)容
```
#import "RiderPDFView.h"
@interface ?RiderPDFView() {
CGPDFDocumentRef ?documentRef;//用它來記錄傳遞進(jìn)來的PDF資源數(shù)據(jù)
int ?pageNum;//記錄需要顯示頁碼
}
@end
@implementation ?RiderPDFView
//這個(gè)方法就不多說了……
- (instancetype)initWithFrame:(CGRect)frame documentRef:(CGPDFDocumentRef)docRef andPageNum:(int)page {
self= [superinitWithFrame:frame];
documentRef= docRef;
pageNum= page;
self.backgroundColor= [UIColorwhiteColor];
returnself;
}
//重寫- (void)drawRect:(CGRect)rect方法
- (void)drawRect:(CGRect)rect {
[selfdrawPDFIncontext:UIGraphicsGetCurrentContext()];//將當(dāng)前的上下文環(huán)境傳遞到方法中舱污,用于繪圖
}
//- (void)drawRect:(CGRect)rect具體的內(nèi)容
- (void)drawPDFIncontext:(CGContextRef)context {
CGContextTranslateCTM(context,0.0,self.frame.size.height);
CGContextScaleCTM(context,1.0, -1.0);
//上面兩句是對(duì)環(huán)境做一個(gè)仿射變換,如果不執(zhí)行上面兩句那么繪制出來的PDF文件會(huì)呈倒置效果扳缕,第二句的作用是使圖形呈正立顯示慌闭,第一句是調(diào)整圖形的位置,如不執(zhí)行繪制的圖形會(huì)不在視圖可見范圍內(nèi)
CGPDFPageRef ?pageRef =CGPDFDocumentGetPage(documentRef,pageNum);//獲取需要繪制的頁碼的數(shù)據(jù)躯舔。兩個(gè)參數(shù)驴剔,第一個(gè)數(shù)傳遞進(jìn)來的PDF資源數(shù)據(jù),第二個(gè)是傳遞進(jìn)來的需要顯示的頁碼
CGContextSaveGState(context);//記錄當(dāng)前繪制環(huán)境粥庄,防止多次繪畫
CGAffineTransform ?pdfTransForm =CGPDFPageGetDrawingTransform(pageRef,kCGPDFCropBox,self.bounds,0,true);//創(chuàng)建一個(gè)仿射變換的參數(shù)給函數(shù)丧失。第一個(gè)參數(shù)是對(duì)應(yīng)頁數(shù)據(jù);第二個(gè)參數(shù)是個(gè)枚舉值惜互,我每個(gè)都試了一下布讹,貌似沒什么區(qū)別……但是網(wǎng)上看的資料都用的我當(dāng)前這個(gè),所以就用這個(gè)了训堆;第三個(gè)參數(shù)描验,是圖形繪制的區(qū)域,我設(shè)置的是當(dāng)前視圖整個(gè)區(qū)域坑鱼,如果有需要膘流,自然是可以修改的絮缅;第四個(gè)是旋轉(zhuǎn)的度數(shù),這里不需要旋轉(zhuǎn)了呼股,所以設(shè)置為0耕魄;第5個(gè),傳遞true彭谁,會(huì)保持長寬比
CGContextConcatCTM(context, pdfTransForm);//把創(chuàng)建的仿射變換參數(shù)和上下文環(huán)境聯(lián)系起來
CGContextDrawPDFPage(context, pageRef);//把得到的指定頁的PDF數(shù)據(jù)繪制到視圖上
CGContextRestoreGState(context);//恢復(fù)圖形狀態(tài)
}
```
以上就是自定義的view中所有需要的東西吸奴,這里我們已經(jīng)完成了PDF文件的繪制。接下來就是如何更好的展示得到的視圖了缠局。
因?yàn)榈玫降腜DF文件可能字體很小则奥,特別是在iPhone上面,所以你大概必不可少的需要給他添加一個(gè)放大狭园、縮小的功能逞度,不然直接用webVie加載不就好了,干嘛還做這么多事情妙啃。
說到放大縮小档泽,我第一時(shí)間想到的就是用UISCrollView來做,用它可比自定義捏合手勢(shì)方便多了揖赴。
我首先用了UISCrollView嵌套UISCrollView的方法馆匿,一個(gè)UISCrollView中嵌套三個(gè)UISCrollView,目的倒是達(dá)到了燥滑,但是代碼量和邏輯處理都太多渐北,而且就在做成功的那一刻,我想起了铭拧,還有個(gè)東西叫UIColectionView……于是果斷棄暗投明赃蛛,使用UICollectionView重做了一次,內(nèi)存占用減少了1/2搀菩,所以我們這里使用UICollectionView來完成功能呕臂。你基本沒用過UICollectionView?別慌肪跋,簡(jiǎn)單的要死歧蒋。
為了達(dá)成我們放大縮小的功能,我們需要自定義一個(gè)UICollectionViewCell州既,那么我們新建一個(gè)類谜洽,繼承自UICollectionViewCell,這里我命名為CollectionViewCell吴叶,以下是它.h文件中的內(nèi)容:
```
#import <UIKit/UIKit.h>
@class ?CollectionViewCell;
@protocol ?collectionCellDelegate
@optional
- (void)collectioncellTaped:(CollectionViewCell*)cell;
@end
//上面是一個(gè)代理協(xié)議阐虚,某個(gè)CollectionViewCell被單擊時(shí)候的回調(diào),你可能是需要的蚌卤,也可能不需要
@interface ?CollectionViewCell :UICollectionViewCell
@property(nonatomic,strong) UIScrollView *contentScrollView; //用于實(shí)現(xiàn)縮放功能的UISCrollView
@property(nonatomic,strong) UIView *showView;//這個(gè)就是現(xiàn)實(shí)PDF文件內(nèi)容的視圖
@property(nonatomic,weak)id <collectionCellDelegate> cellTapDelegate;//代理
@end
```
然后是它.m文件中的內(nèi)容
```
#import "CollectionViewCell.h"
#import "RiderPDFView.h"
@interface CollectionViewCell()<UIScrollViewDelegate>//遵守UISCrollViewDelegate協(xié)議实束,這樣才能實(shí)現(xiàn)縮放
@end
@implementation CollectionViewCell
//重寫init方法
- (instancetype)initWithFrame:(CGRect)frame {
if(self= [superinitWithFrame:frame]) {
_contentScrollView= [[UIScrollView alloc]initWithFrame:self.bounds];//初始化_contentScrollView
_contentScrollView.contentSize= frame.size;//設(shè)置_contentScrollView的內(nèi)容尺寸
_contentScrollView.minimumZoomScale=0.5;//設(shè)置最小縮放比例
_contentScrollView.maximumZoomScale=2.5;//設(shè)置最大的縮放比例
_contentScrollView.delegate=self;//設(shè)置代理
[self.contentView addSubview:_contentScrollView];//將_contentScrollView添加到CollectionViewCell中
UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(cellClicked)];//創(chuàng)建手勢(shì)
[self addGestureRecognizer: tapGes];//添加手勢(shì)到CollectionViewCell上
}
returnself;
}
//這是scrollView的代理方法贸宏,實(shí)現(xiàn)后才能通過scrollView實(shí)現(xiàn)縮放
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView {
for(UIView*view in scrollView.subviews) {
if([view isKindOfClass:[RiderPDFView class]]) {
return view;//返回需要被縮放的視圖
}
}
returnnil;
}
//重寫set方法
- (void)setShowView:(UIView*)showView {
for(UIView*tempView in _contentScrollView.subviews) {
[tempView removeFromSuperview];//移除_contentScrollView中的所有視圖
}
_showView= showView;賦值
[_contentScrollView addSubview:showView];//將需要顯示的視圖添加到_contentScrollView上
}
//tap事件
- (void)cellClicked {
if([self.cellTapDelegaterespondsToSelector:@selector(collectioncellTaped:)]) {
[self.cellTapDelegatecollectioncellTaped:self];
}
}
```
以上已完成了所有的準(zhǔn)備工作,接下來就是在一個(gè)視圖控制器中完整的展示獲得的PDF文件了磕洪,下面是一個(gè)ViewController.m中的內(nèi)容:
```
#import "ViewController.h"
#import "CollectionViewCell.h"http://導(dǎo)入自定義的CollectionViewCell
#import "RiderPDFView.h"http://導(dǎo)入展示PDF文件內(nèi)容的View
@interface ViewController()<UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,UIScrollViewDelegate,collectionCellDelegate>//遵守協(xié)議
{
UICollectionView *testCollectionView; //展示用的CollectionView
CGPDFDocumentRef _docRef; 需要獲取的PDF資源文件
}
@property(nonatomic,strong)NSMutableArray*dataArray;//存數(shù)據(jù)的數(shù)組
@property(nonatomic,assign)int totalPage;//一共有多少頁
@end
@implementationViewController
- (void)viewDidLoad {
[superviewDidLoad];
_docRef=test(@"http://teaching.csse.uwa.edu.au/units/CITS4401/practicals/James1_files/SPMP1.pdf");//通過test函數(shù)獲取PDF文件資源,test函數(shù)的實(shí)現(xiàn)為我們最上面的方法诫龙,當(dāng)然下面又寫了一遍
[self getDataArrayValue];//獲取需要展示的數(shù)據(jù)
UICollectionViewFlowLayout*layout = [[UICollectionViewFlowLayout alloc]init];//UICollectionView需要在創(chuàng)建的時(shí)候傳入一個(gè)布局參數(shù)析显,故在創(chuàng)建它之前,先創(chuàng)建一個(gè)布局签赃,這里使用系統(tǒng)的布局就好
layout.itemSize=self.view.frame.size;//設(shè)置CollectionView中每個(gè)item及集合視圖中每單個(gè)元素的大小谷异,我們每個(gè)視圖使用一頁來顯示,所以設(shè)置為當(dāng)前視圖的大小
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];//設(shè)置滑動(dòng)方向?yàn)樗椒较蚪趿模部梢栽O(shè)置為豎直方向
layout.minimumLineSpacing=0;//設(shè)置item之間最下行距
layout.minimumInteritemSpacing=0;//設(shè)置item之間最小間距
testCollectionView= [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:layout];//創(chuàng)建一個(gè)集合視圖歹嘹,設(shè)置其大小為當(dāng)前view的大小,布局為上面我們創(chuàng)建的布局
testCollectionView.pagingEnabled=YES;//設(shè)置集合視圖一頁一頁的翻動(dòng)
[testCollectionView registerClass:[CollectionViewCell class]forCellWithReuseIdentifier:@"test"];//為集合視圖注冊(cè)單元格
testCollectionView.delegate=self;//設(shè)置代理
testCollectionView.dataSource=self;//設(shè)置數(shù)據(jù)源
[self.view addSubview:testCollectionView];//將集合視圖添加到當(dāng)前視圖上
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
}
//通過地址字符串獲取PDF資源
CGPDFDocumentReftest(NSString*fileName) {
NSURL*url = [NSURLURLWithString:fileName];
CFURLRefrefURL = (__bridge_retainedCFURLRef)url;
CGPDFDocumentRefdocument =CGPDFDocumentCreateWithURL(refURL);
CFRelease(refURL);
if(document) {
returndocument;
}else{
returnNULL;
}
}
//獲取所有需要顯示的PDF頁面
- (void)getDataArrayValue {
size_t totalPages =CGPDFDocumentGetNumberOfPages(_docRef);//獲取總頁數(shù)
self.totalPage= (int)totalPages;//給全局變量賦值
NSMutableArray*arr = [NSMutableArray new];
//通過循環(huán)創(chuàng)建需要顯示的PDF頁面孔庭,并把這些頁面添加到數(shù)組中
for(inti =1; i <= totalPages; i++) {
RiderPDFView *view = [[RiderPDFView alloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height) documentRef: _docRef andPageNum:i];
[arr addObject:view];
}
self.dataArray= arr;//給數(shù)據(jù)數(shù)組賦值
}
//返回集合視圖共有幾個(gè)分區(qū)
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView {
return1;
}
//返回集合視圖中一共有多少個(gè)元素——自然是總頁數(shù)
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
return self.totalPage;
}
//復(fù)用尺上、返回cell
- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath*)indexPath {
CollectionViewCell*cell = [collectionViewdequeueReusableCellWithReuseIdentifier:@"test"forIndexPath:indexPath];
cell.cellTapDelegate=self;//設(shè)置tap事件代理
cell.showView=self.dataArray[indexPath.row];//賦值,設(shè)置每個(gè)item中顯示的內(nèi)容
returncell;
}
//當(dāng)集合視圖的item被點(diǎn)擊后觸發(fā)的事件圆到,根據(jù)個(gè)人需求寫
- (void)collectioncellTaped:(CollectionViewCell*)cell {
NSLog(@"我點(diǎn)了咋的怎抛?");
}
//集合視圖繼承自scrollView,所以可以用scrollView 的代理事件芽淡,這里的功能是當(dāng)某個(gè)item不在當(dāng)前視圖中顯示的時(shí)候马绝,將它的縮放比例還原
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
for(UIView *view in testCollectionView.subviews) {
if([view isKindOfClass:[CollectionViewCell class]]) {
CollectionViewCell*cell = (CollectionViewCell*)view;
[cell.contentScrollViewsetZoomScale:1.0];
}
}
}
@end
```
然后……然后就沒有然后了,功能實(shí)現(xiàn)了挣菲,如果還需要其它功能富稻,可以再此基礎(chǔ)上添加,反正我暫時(shí)沒更多的需求了白胀,以上椭赋。
后記:公司的PDF文件突然大了很多,發(fā)現(xiàn)一次性繪制PDF文件所有頁面會(huì)導(dǎo)致內(nèi)存占用飆升或杠,分分鐘崩潰纹份,所以最后優(yōu)化,加載的時(shí)候只在某一頁需要顯示的時(shí)候才從PDF文件源中去加載起資源廷痘,這樣就好了蔓涧。
關(guān)于放大的問題,給兩個(gè)建議笋额,使用PDF數(shù)據(jù)去繪圖元暴,繪制完成是不會(huì)模糊的。有些朋友繪制完成后兄猩,對(duì)視圖進(jìn)行放大操作茉盏,導(dǎo)致了模糊鉴未,那么可以考慮:
1:將視圖本身就繪制在一個(gè)較大的視圖上,然后縮小視圖進(jìn)行顯示鸠姨,再次放大的時(shí)候铜秆,問題自然沒了。
2:監(jiān)控縮放的比例讶迁,當(dāng)縮放進(jìn)行到一定程度(可能模糊的程度)连茧,就調(diào)用PDF數(shù)據(jù)源,在放大的視圖上進(jìn)行重繪巍糯。
如有錯(cuò)漏啸驯,歡迎指正,拒絕撕逼祟峦,3q罚斗。