前言
在我們的開發(fā)中,有些像電子書類型的app的開發(fā)會涉及到pdf文檔的加載與展示。由于筆者項目中正好涉及到這塊,于是將pdf常用的幾種加載方式做個總結(jié)。以供后面可能用到的同學做個參考。
正文
通常我們用到的pdf文檔的加載方式有4種:
- UIWebView加載本地或者網(wǎng)絡pdf文檔
- QLPreviewController加載pdf文檔
- 用CGContext畫pdf文檔,并結(jié)合UIPageViewController展示
- 第三方框架vfr/Reader加載pdf文檔
下面就按照上面4種方式的順序依次介紹具體的用法。
UIWebView加載本地或者網(wǎng)絡pdf文檔
UIWebView加載pdf文檔比較簡單,加載本地文檔和網(wǎng)絡文檔用法幾乎差不多。
瀏覽方式是上下拖動,支持放大縮小,以及選擇copy等。
加載本地文檔:
//初始化myWebView
UIWebView *myWebView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
myWebView.backgroundColor = [UIColor whiteColor];
NSURL *filePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"myHome" ofType:@"pdf"]];
NSURLRequest *request = [NSURLRequest requestWithURL: filePath];
[myWebView loadRequest:request];
//使文檔的顯示范圍適合UIWebView的bounds
[myWebView setScalesPageToFit:YES];
加載網(wǎng)絡文檔:
//初始化myWebView
UIWebView *myWebView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
myWebView.backgroundColor = [UIColor whiteColor];
NSURL *filePath = [NSURL URLWithString:@"https://www.tutorialspoint.com/ios/ios_tutorial.pdf"];
NSURLRequest *request = [NSURLRequest requestWithURL: filePath];
[myWebView loadRequest:request];
//使文檔的顯示范圍適合UIWebView的bounds
[myWebView setScalesPageToFit:YES];
QLPreviewController加載pdf文檔
在iOS 4 SDK之后蘋果退出了QLPreviewControllerAPI,組件允許用戶瀏覽許多不同的文件類型,如XLS文件,Word文檔文件,PDF文件等,但是使用此API之前用戶必須導入QuickLook.framework框架,使用的QLPreviewController時,你必須實現(xiàn)此協(xié)議QLPreviewControllerDataSource的兩個代理方法。
上下滑動支持單個文檔的瀏覽,左右滑動支持不同文檔間的切換,還支持蘋果自帶的分享打印等。
QLPreviewControllerDataSource的兩個代理方法:
/*
*所要加載pdf文檔的個數(shù)
*/
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller;
/*
* 返回每個index pdf文檔所對應的文檔路徑
*/
- (id <QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index;
QLPreviewController加載pdf文檔
//QLPreviewController初始化,需要導入QuickLook.framework
QLPreviewController *QLPVC = [[QLPreviewController alloc] init];
QLPVC.dataSource = self;
[self presentViewController:QLPVC animated:YES completion:nil];
#pragma mark QLPreviewControllerDataSource
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller{
return 2;
}
- (id<QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index{
NSArray *arr = @[FILE_PATH,FILE_PATH1];
return [NSURL fileURLWithPath:arr[index]];
}
用CGContext畫pdf文檔,并結(jié)合UIPageViewController展示
首先將pdf單頁的文檔畫在UIView的畫布上:
//CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("test.pdf"), NULL, NULL);
CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), (__bridge CFStringRef)self.fileName, NULL, NULL);
//創(chuàng)建CGPDFDocument對象
CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
//獲取當前的上下文
CGContextRef *context = UIGraphicsGetCurrentContext();
//Quartz坐標系和UIView坐標系不一樣所致,調(diào)整坐標系,使pdf正立
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
//獲取指定頁的pdf文檔
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, pageNO);
//創(chuàng)建一個仿射變換,該變換基于將PDF頁的BOX映射到指定的矩形中。
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, self.bounds, 0, true);
CGContextConcatCTM(context, pdfTransform);
//將pdf繪制到上下文中
CGContextDrawPDFPage(context, page);
用UIPageViewController展示分頁的pdf文檔
//初始化PDFPageModel
pdfPageModel = [[CGContextDrawPDFPageModel alloc] initWithPDFDocument:pdfDocument];
// UIPageViewControllerSpineLocationMin 單頁顯示
NSDictionary *options = [NSDictionary dictionaryWithObject:
[NSNumber numberWithInteger: UIPageViewControllerSpineLocationMin]
forKey: UIPageViewControllerOptionSpineLocationKey];
//初始化UIPageViewController,UIPageViewControllerTransitionStylePageCurl翻頁效果,UIPageViewControllerNavigationOrientationHorizontal水平方向翻頁
pageViewCtrl = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:options];
//承載pdf每頁內(nèi)容的控制器
CGContextDrawPDFPageController *initialViewController = [pdfPageModel viewControllerAtIndex:1];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
//設置UIPageViewController的數(shù)據(jù)源
[pageViewCtrl setDataSource:pdfPageModel];
//pageViewCtrl.doubleSided = YES;設置正反面都有文字
//設置pageViewCtrl的子控制器
[pageViewCtrl setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionReverse
animated:NO
completion:^(BOOL f){}];
[self addChildViewController:pageViewCtrl];
[self.view addSubview:pageViewCtrl.view];
//當我們向我們的視圖控制器容器(就是父視圖控制器,它調(diào)用addChildViewController方法加入子視圖控制器,它就成為了視圖控制器的容器)中添加(或者刪除)子視圖控制器后,必須調(diào)用該方法,告訴iOS,已經(jīng)完成添加(或刪除)子控制器的操作。
[pageViewCtrl didMoveToParentViewController:self];
//CGContextDrawPDFPageModel.m
//獲得pdfDocument的總頁數(shù)
long pageSum = CGPDFDocumentGetNumberOfPages(pdfDocument);
#pragma mark返回pageViewController當前頁前一頁的代理方法(如果要每頁的背面顯示與正面相同的風格,而不是默認的白,需要設置pageController的doubleSide屬性為YES,同時在下面的兩個代理方法中設置BackViewController)
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSUInteger index = [self indexOfViewController: (CGContextDrawPDFPageController *)viewController];
if ((index == 1) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
#pragma mark返回pageViewController當前頁后一頁的代理方法
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger index = [self indexOfViewController: (CGContextDrawPDFPageController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
//獲取pdf文檔的頁數(shù)
long pageSum = CGPDFDocumentGetNumberOfPages(pdfDocument);
if (index >= pageSum+1) {
return nil;
}
return [self viewControllerAtIndex:index];
}
也許我們在平時會注意到,一些電子書閱讀器的翻頁過程中會有白天模式和夜間模式,而UIPageViewController默認的翻頁效果如下:
如果黑夜模式也是這種默認的效果如圖就會很尷尬:
為了解決這種問題:
需要將UIPageViewController的doubleSided屬性設為YES,然后將當前視圖截圖放在每頁的背面這樣翻頁的過程中背面的效果就和相應的模式對應了。
主要修改兩個地方:
第一:為設置背面的視圖新建一個控制器,同時在控制器上加載一個UIImageView,圖片設置為圖書當前頁的截圖,具體實現(xiàn)如下:
- (void)updateWithViewController:(UIViewController *)viewController {
self.backgroundImage = [self captureView:viewController.view];
}
- (UIImage *)captureView:(UIView *)view {
CGRect rect = view.bounds;
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, rect.size.width, 0.0);
CGContextConcatCTM(context,transform);
[view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
第二:在UIPageViewController的dataSource的代理方法中,設置背頁為放截圖的控制器。
#pragma mark如果要每頁的背面顯示與正面相同的風格,而不是默認的白,需要設置pageController的doubleSide屬性為YES,同時在下面的兩個代理方法中設置BackViewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if([viewController isKindOfClass:[CGContextDrawPDFPageController class]]) {
self.currentViewController = viewController;
BackViewController *backViewController = [[BackViewController alloc] init];
[backViewController updateWithViewController:viewController];
return backViewController;
}
//self.currentViewController保存的是后一個CGContextDrawPDFPageController,如果直接用viewController實際指的是backViewController,而其沒有indexOfViewController:等方法程序會崩掉。
NSUInteger index = [self indexOfViewController: (CGContextDrawPDFPageController *)self.currentViewController];
if ((index == 1) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if([viewController isKindOfClass:[CGContextDrawPDFPageController class]]) {
self.currentViewController = viewController;
BackViewController *backViewController = [[BackViewController alloc] init];
[backViewController updateWithViewController:viewController];
return backViewController;
}
//self.currentViewController保存的是前一個CGContextDrawPDFPageController,如果直接用viewController實際指的是backViewController,而其沒有indexOfViewController:等方法程序會崩掉。
NSUInteger index = [self indexOfViewController: (CGContextDrawPDFPageController *)self.currentViewController];
if (index == NSNotFound) {
return nil;
}
index++;
//獲取pdf文檔的頁數(shù)
long pageSum = CGPDFDocumentGetNumberOfPages(pdfDocument);
if (index >= pageSum+1) {
return nil;
}
return [self viewControllerAtIndex:index];
}
第三方框架vfr/Reader加載pdf文檔
使用第三方框架vfr/Reader加載pdf文檔非常簡單易用,集成了打印,分享,發(fā)郵件,預覽等多種功能,直接上代碼如下:
//Reader初始化 加載本地pdf文件
ReaderDocument *doc = [[ReaderDocument alloc] initWithFilePath:FILE_PATH password:nil];
ReaderViewController *rederVC = [[ReaderViewController alloc] initWithReaderDocument:doc];
rederVC.delegate = self;
rederVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
rederVC.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self presentViewController:rederVC animated:YES completion:nil];
#pragma mark ReaderViewControllerDelegate因為PDF閱讀器可能是push出來的,也可能是present出來的,為了更好的效果,這個代理方法可以實現(xiàn)很好的退出
- (void)dismissReaderViewController:(ReaderViewController *)viewController{
[self dismissViewControllerAnimated:YES completion:nil];
}
源碼已上傳至fenglinyunshi-git,歡迎下載,并提出寶貴意見。
結(jié)語
加載pdf文件可能還有更多的實現(xiàn)方式,歡迎補充,如有不準確的地方還望指正,謝謝。
問渠那得清如許,為有源頭活水來。