iOS开发之pdf文档的加载与浏览的4种方式

前言

在我们的开发中,有些像电子书类型的app的开发会涉及到pdf文档的加载与展示。由于笔者项目中正好涉及到这块,于是将pdf常用的几种加载方式做个总结。以供后面可能用到的同学做个参考。

正文

通常我们用到的pdf文档的加载方式有4种:

  • UIWebView加载本地或者网络pdf文档
  • QLPreviewController加载pdf文档
  • 用CGContext画pdf文档,并结合UIPageViewController展示
  • 第三方框架vfr/Reader加载pdf文档

下面就按照上面4种方式的顺序依次介绍具体的用法。

UIWebView加载本地或者网络pdf文档

UIWebView加载pdf文档比较简单,加载本地文档和网络文档用法几乎差不多。
浏览方式是上下拖动,支持放大缩小,以及选择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];

加载网络文档:

//初始化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时,你必须实现此协议QLPreviewControllerDataSource的两个代理方法。
上下滑动支持单个文档的浏览,左右滑动支持不同文档间的切换,还支持苹果自带的分享打印等。
QLPreviewControllerDataSource的两个代理方法:

/*
 *所要加载pdf文档的个数
 */
- (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文档,并结合UIPageViewController展示

首先将pdf单页的文档画在UIView的画布上:

//CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("test.pdf"), NULL, NULL);
    CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), (__bridge CFStringRef)self.fileName, NULL, NULL);
//创建CGPDFDocument对象
CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);

//获取当前的上下文
CGContextRef *context = UIGraphicsGetCurrentContext();
//Quartz坐标系和UIView坐标系不一样所致,调整坐标系,使pdf正立
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

 //获取指定页的pdf文档
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, pageNO);
//创建一个仿射变换,该变换基于将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每页内容的控制器
CGContextDrawPDFPageController *initialViewController = [pdfPageModel viewControllerAtIndex:1];
 NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
//设置UIPageViewController的数据源
 [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];
    //当我们向我们的视图控制器容器(就是父视图控制器,它调用addChildViewController方法加入子视图控制器,它就成为了视图控制器的容器)中添加(或者删除)子视图控制器后,必须调用该方法,告诉iOS,已经完成添加(或删除)子控制器的操作。
    [pageViewCtrl didMoveToParentViewController:self];

//CGContextDrawPDFPageModel.m
//获得pdfDocument的总页数
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文档的页数
    long pageSum = CGPDFDocumentGetNumberOfPages(pdfDocument);
    if (index >= pageSum+1) {
        return nil;
    }
    return [self viewControllerAtIndex:index];
}

也许我们在平时会注意到,一些电子书阅读器的翻页过程中会有白天模式和夜间模式,而UIPageViewController默认的翻页效果如下:

翻页默认效果.png

如果黑夜模式也是这种默认的效果如图就会很尴尬:

黑夜模式.jpeg

为了解决这种问题:
需要将UIPageViewController的doubleSided属性设为YES,然后将当前视图截图放在每页的背面这样翻页的过程中背面的效果就和相应的模式对应了。
主要修改两个地方:
第一:为设置背面的视图新建一个控制器,同时在控制器上加载一个UIImageView,图片设置为图书当前页的截图,具体实现如下:

- (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文档的页数
    long pageSum = CGPDFDocumentGetNumberOfPages(pdfDocument);
    if (index >= pageSum+1) {
        return nil;
    }
    return [self viewControllerAtIndex:index];
}
第三方框架vfr/Reader加载pdf文档

使用第三方框架vfr/Reader加载pdf文档非常简单易用,集成了打印,分享,发邮件,预览等多种功能,直接上代码如下:

//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出来的,为了更好的效果,这个代理方法可以实现很好的退出
- (void)dismissReaderViewController:(ReaderViewController *)viewController{
    [self dismissViewControllerAnimated:YES completion:nil];
}

源码已上传至fenglinyunshi-git,欢迎下载,并提出宝贵意见。

结语

加载pdf文件可能还有更多的实现方式,欢迎补充,如有不准确的地方还望指正,谢谢。

问渠那得清如许,为有源头活水来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容