1. 程式人生 > >iOS開發:PDF檔案的載入及顯示(網路、本地)

iOS開發:PDF檔案的載入及顯示(網路、本地)

話說,公司專案需要顯示PDF檔案,於是遍尋了網路,發現的方法以下幾種:

1.使用UIWebView載入,沒啥說的,根據檔案路徑,網路或者本地皆可,建立一個NSURLRequest,然後用webView載入就可以了,但僅僅能顯示檔案,很low;

2.使用UIDocumentInteractionController或QLPreviewController進行預覽——依然可以很方便的檢視PDF文件,還有些系統自帶的方法和功能,但是這依舊不是我要的。

3.使用第三方—— Reader(vfr) ,功能很強大,可配置性高,遠遠超出了我需要的範疇,不能更完美了。然而,就在我高高興興的準備把它加入專案的時候,才發現這貨不能載入網路上的PDF資源--|||於是我嘗試把網路檔案下載到了本地,寫入了沙盒cache目錄,然後還是不能載入……使勁的找了一下,似乎應該寫到沙盒的doc目錄,於是又把檔案下載到了doc目錄,然後依舊不能載入%>

<%好吧,只好自己動手了。(有兄弟姐妹成功使用過,萬望指教)

4.自己寫,當然這也是這篇文章要寫的,功能包含:獲取網路/本地PDF檔案並顯示,可以實現放大或縮小顯示的效果。

以下上貨:

專案思路(這很重要,只抄程式碼自然是能實現功能的,但是……):

第一步,自然是獲取pdf資源。Bundle中的資源就不說了,如果你的PDF檔案都是存在於Bundle中的,那麼使用 Reader(vfr) 是最好的了,我們這裡只說網路和本地資源(好吧,本地資源和網路資源的區別很簡單的說就在於他們的url地址不同,所以,我們只說網路資源吧,本地資源,對應替換url地址就好了)

下方的方法會通過一個url字串,返回一個CGPDFDocumentRef格式的資料,你可能看到這不是一個OC方法的寫法,是的,它不是。


CGPDFDocumentRef   test(NSString*urlString) {

NSURL*url = [NSURLURLWithString:urlString];//將傳入的字串轉化為一個NSURL地址

CFURLRefrefURL = (__bridge_retainedCFURLRef)url;//將的到的NSURL轉化為CFURLRefrefURL備用

CGPDFDocumentRefdocument =CGPDFDocumentCreateWithURL(refURL);//通過CFURLRefrefURL獲取檔案內容

CFRelease(refURL);//過河拆橋,釋放使用完畢的CFURLRefrefURL,這個東西並不接受自動記憶體管理,所以要手動釋放
if(document) { return document;//返回獲取到的資料 }else{ return NULL; //如果沒獲取到資料,則返回NULL,當然,你可以在這裡新增一些列印日誌,方便你發現問題 } }

第二步,將獲取到的資料顯示出來,好吧,這裡就不是一句話能搞定的了,我們需要細說。

首先,我們獲取到的PDF資源十有八九不僅僅是一頁,而是很多頁,所以肯定不可能在同一個檢視上顯示。那麼我們就需要單獨的獲取到PDF資源資料中某一頁的資料,別慌,系統有專門的函式;然後我們大概還需要知道這個PDF資源一共有多少頁,別慌,這個系統也有專門的函式,我們會在用到的時候說明。

其次,獲取到某一頁的資料後,我們還要把它展示到一個view上面,最後很多個view 的集合就是我們需要展示的所有東西了。

所以,我們需要自定義一個view,然後傳入我們通過上面方法已經獲取到的CGPDFDocumentRef資料和需要顯示的頁數,讓這個view來展示對應頁數的PDF檔案內容。(為什麼需要一個view,因為這裡會用到 Quartz 2D繪圖,需要重寫view的- (void)drawRect:(CGRect)rect方法來實現PDF檔案內容的繪製)

那麼下一步自然是新建一個view,繼承自UIView,這裡我取名為RiderPDFView,以下為.h檔案內容:


#import <UIKit/UIKit.h>

@interface  RiderPDFView :UIView

//寫一個方法,通過Frame、已經獲取到的CGPDFDocumentRef檔案和需要顯示的PDF檔案的頁碼,來建立一個顯示PDF檔案內容的檢視

- (instancetype)initWithFrame:(CGRect)frame documentRef:(CGPDFDocumentRef)docRef andPageNum:(int)page;

@end

然後是自定義的檢視中.m檔案的內容


#import "RiderPDFView.h"

@interface  RiderPDFView() {

CGPDFDocumentRef  documentRef;//用它來記錄傳遞進來的PDF資源資料

int  pageNum;//記錄需要顯示頁碼

}

@end

@implementation  RiderPDFView

//這個方法就不多說了……

- (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()];//將當前的上下文環境傳遞到方法中,用於繪圖

}

//- (void)drawRect:(CGRect)rect具體的內容

- (void)drawPDFIncontext:(CGContextRef)context {

CGContextTranslateCTM(context,0.0,self.frame.size.height);

CGContextScaleCTM(context,1.0, -1.0);

//上面兩句是對環境做一個仿射變換,如果不執行上面兩句那麼繪製出來的PDF檔案會呈倒置效果,第二句的作用是使圖形呈正立顯示,第一句是調整圖形的位置,如不執行繪製的圖形會不在檢視可見範圍內

CGPDFPageRef  pageRef =CGPDFDocumentGetPage(documentRef,pageNum);//獲取需要繪製的頁碼的資料。兩個引數,第一個數傳遞進來的PDF資源資料,第二個是傳遞進來的需要顯示的頁碼

CGContextSaveGState(context);//記錄當前繪製環境,防止多次繪畫

CGAffineTransform  pdfTransForm =CGPDFPageGetDrawingTransform(pageRef,kCGPDFCropBox,self.bounds,0,true);//建立一個仿射變換的引數給函式。第一個引數是對應頁資料;第二個引數是個列舉值,我每個都試了一下,貌似沒什麼區別……但是網上看的資料都用的我當前這個,所以就用這個了;第三個引數,是圖形繪製的區域,我設定的是當前檢視整個區域,如果有需要,自然是可以修改的;第四個是旋轉的度數,這裡不需要旋轉了,所以設定為0;第5個,傳遞true,會保持長寬比

CGContextConcatCTM(context, pdfTransForm);//把建立的仿射變換引數和上下文環境聯絡起來

CGContextDrawPDFPage(context, pageRef);//把得到的指定頁的PDF資料繪製到檢視上

CGContextRestoreGState(context);//恢復圖形狀態

}

以上就是自定義的view中所有需要的東西,這裡我們已經完成了PDF檔案的繪製。接下來就是如何更好的展示得到的檢視了。

因為得到的PDF檔案可能字型很小,特別是在iPhone上面,所以你大概必不可少的需要給他新增一個放大、縮小的功能,不然直接用webVie載入不就好了,幹嘛還做這麼多事情。

說到放大縮小,我第一時間想到的就是用UISCrollView來做,用它可比自定義捏合手勢方便多了。

我首先用了UISCrollView巢狀UISCrollView的方法,一個UISCrollView中巢狀三個UISCrollView,目的倒是達到了,但是程式碼量和邏輯處理都太多,而且就在做成功的那一刻,我想起了,還有個東西叫UIColectionView……於是果斷棄暗投明,使用UICollectionView重做了一次,記憶體佔用減少了1/2,所以我們這裡使用UICollectionView來完成功能。你基本沒用過UICollectionView?別慌,簡單的要死。

為了達成我們放大縮小的功能,我們需要自定義一個UICollectionViewCell,那麼我們新建一個類,繼承自UICollectionViewCell,這裡我命名為CollectionViewCell,以下是它.h檔案中的內容:


#import <UIKit/UIKit.h>

@class  CollectionViewCell;

@protocol  collectionCellDelegate

@optional

- (void)collectioncellTaped:(CollectionViewCell*)cell;

@end

//上面是一個代理協議,某個CollectionViewCell被單擊時候的回撥,你可能是需要的,也可能不需要

@interface  CollectionViewCell :UICollectionViewCell

@property(nonatomic,strong) UIScrollView *contentScrollView; //用於實現縮放功能的UISCrollView

@property(nonatomic,strong) UIView *showView;//這個就是現實PDF檔案內容的檢視

@property(nonatomic,weak)id <collectionCellDelegate> cellTapDelegate;//代理

@end

然後是它.m檔案中的內容


#import "CollectionViewCell.h"

#import "RiderPDFView.h"

@interface CollectionViewCell()<UIScrollViewDelegate>//遵守UISCrollViewDelegate協議,這樣才能實現縮放

@end

@implementation CollectionViewCell

//重寫init方法

- (instancetype)initWithFrame:(CGRect)frame {

if(self= [superinitWithFrame:frame]) {

_contentScrollView= [[UIScrollView alloc]initWithFrame:self.bounds];//初始化_contentScrollView

_contentScrollView.contentSize= frame.size;//設定_contentScrollView的內容尺寸

_contentScrollView.minimumZoomScale=0.5;//設定最小縮放比例

_contentScrollView.maximumZoomScale=2.5;//設定最大的縮放比例

_contentScrollView.delegate=self;//設定代理

[self.contentView addSubview:_contentScrollView];//將_contentScrollView新增到CollectionViewCell中

UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(cellClicked)];//建立手勢

[self addGestureRecognizer: tapGes];//新增手勢到CollectionViewCell上

}

returnself;

}

//這是scrollView的代理方法,實現後才能通過scrollView實現縮放

- (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];

}

}

以上已完成了所有的準備工作,接下來就是在一個檢視控制器中完整的展示獲得的PDF檔案了,下面是一個ViewController.m中的內容:


#import "ViewController.h"

#import "CollectionViewCell.h"//匯入自定義的CollectionViewCell

#import "RiderPDFView.h"//匯入展示PDF檔案內容的View

@interface ViewController()<UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,UIScrollViewDelegate,collectionCellDelegate>//遵守協議

{

UICollectionView *testCollectionView; //展示用的CollectionView

CGPDFDocumentRef _docRef; 需要獲取的PDF資原始檔

}

@property(nonatomic,strong)NSMutableArray*dataArray;//存資料的陣列

@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函式獲取PDF檔案資源,test函式的實現為我們最上面的方法,當然下面又寫了一遍

[self getDataArrayValue];//獲取需要展示的資料

UICollectionViewFlowLayout*layout = [[UICollectionViewFlowLayout alloc]init];//UICollectionView需要在建立的時候傳入一個佈局引數,故在建立它之前,先建立一個佈局,這裡使用系統的佈局就好

layout.itemSize=self.view.frame.size;//設定CollectionView中每個item及集合檢視中每單個元素的大小,我們每個檢視使用一頁來顯示,所以設定為當前檢視的大小

[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];//設定滑動方向為水平方向,也可以設定為豎直方向

layout.minimumLineSpacing=0;//設定item之間最下行距

layout.minimumInteritemSpacing=0;//設定item之間最小間距

testCollectionView= [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:layout];//建立一個集合檢視,設定其大小為當前view的大小,佈局為上面我們建立的佈局

testCollectionView.pagingEnabled=YES;//設定集合檢視一頁一頁的翻動

[testCollectionView registerClass:[CollectionViewCell class]forCellWithReuseIdentifier:@"test"];//為集合檢視註冊單元格

testCollectionView.delegate=self;//設定代理

testCollectionView.dataSource=self;//設定資料來源

[self.view addSubview:testCollectionView];//將集合檢視新增到當前檢視上

}

- (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);//獲取總頁數

self.totalPage= (int)totalPages;//給全域性變數賦值

NSMutableArray*arr = [NSMutableArray new];

//通過迴圈建立需要顯示的PDF頁面,並把這些頁面新增到陣列中

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;//給資料陣列賦值

}

//返回集合檢視共有幾個分割槽

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView*)collectionView {

return1;

}

//返回集合檢視中一共有多少個元素——自然是總頁數

- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {

return self.totalPage;

}

//複用、返回cell

- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView cellForItemAtIndexPath:(NSIndexPath*)indexPath {

CollectionViewCell*cell = [collectionViewdequeueReusableCellWithReuseIdentifier:@"test"forIndexPath:indexPath];

cell.cellTapDelegate=self;//設定tap事件代理

cell.showView=self.dataArray[indexPath.row];//賦值,設定每個item中顯示的內容

returncell;

}

//當集合檢視的item被點選後觸發的事件,根據個人需求寫

- (void)collectioncellTaped:(CollectionViewCell*)cell {

NSLog(@"我點了咋的?");

}

//集合檢視繼承自scrollView,所以可以用scrollView 的代理事件,這裡的功能是當某個item不在當前檢視中顯示的時候,將它的縮放比例還原

- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {

for(UIView *view in testCollectionView.subviews) {

if([view isKindOfClass:[CollectionViewCell class]]) {

CollectionViewCell*cell = (CollectionViewCell*)view;

[cell.contentScrollViewsetZoomScale:1.0];

}

}

}

@end

然後……然後就沒有然後了,功能實現了,如果還需要其它功能,可以再此基礎上新增,反正我暫時沒更多的需求了,以上。

未完待續。。。