美文网首页iOS Collection理论OC开发
iOS开发之pdf文档的加载与浏览的4种方式

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

作者: 绿豆粥与茶叶蛋 | 来源:发表于2017-04-10 12:23 被阅读5930次

    前言

    在我们的开发中,有些像电子书类型的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] init];
        myWebView.backgroundColor = [UIColor whiteColor];
        NSURL *filePath = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"myHome" ofType:@"pdf"]];
        NSURLRequest *request = [NSURLRequest requestWithURL: filePath];
        [myWebView loadRequest:request];
        //使文档的显示范围适合UIWebView的bounds
        [myWebView setScalesPageToFit:YES];
    

    加载网络文档:

    //初始化myWebView
        UIWebView *myWebView = [[UIWebView alloc] init];
        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文件可能还有更多的实现方式,欢迎补充,如有不准确的地方还望指正,谢谢。

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

    相关文章

      网友评论

      • wpwb110:你好,在使用CGContextDrawPDFPage会有内存泄漏,请问怎么解决
        //获取指定页的pdf文档
        CGPDFPageRef page = CGPDFDocumentGetPage(self.pdfref, i);
        //创建一个仿射变换,该变换基于将PDF页的BOX映射到指定的矩形中。
        CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, targetView.bounds, 0, true);
        CGContextConcatCTM(ctx, pdfTransform);
        CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
        CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);
        CGContextSaveGState(ctx);
        //将pdf绘制到上下文中
        CGContextDrawPDFPage(ctx, page);

        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        [imageArray addObject:image];
        CGContextClearRect(ctx, CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height));
        CGContextRestoreGState(ctx);
        CGContextSynchronize(ctx);

        CGPDFPageRelease(page);
      • 睿少:你好,如果要实现搜索PDF里的文字有什么好方法没?
      • 驿路梨花处处开:1:第一种方式加载本地的pdf 方式,应该优先选择wkwebview ,
        2: 你webview初始化并没有给frame ,加载到哪里去?这是很严重的错误。
        3: 将文档路径转成url 你确定是[NSurl urlWithString:] 这样的方式?建议你自己测试完毕再发表文章,这样更有说服力。
      • 回眸月:用CGContext画pdf文档,这种方式怎么修改字体大小呢?我们现在做的有点击按钮放大缩小以及手势缩放功能,求思路
        绿豆粥与茶叶蛋:@回眸月 可以准备几份字体大小不同的pdf文档?
        绿豆粥与茶叶蛋:@回眸月 这种方式只能整体缩放每页的大小,不能只缩放字体,因为它是按照pdf整页画在view上的,可以通过改变view的大小来改变显示内容的大小,但是整体布局还是和pdf页面一样的。
      • 2245b370b88e:reader 能不能 只显示一 指定的几页 ,比如一个pdf 有10页,然后我只显示2,3,4 页
        绿豆粥与茶叶蛋:@心于止此 已加你
        2245b370b88e:@绿豆粥与茶叶蛋 加个qq 求指导 1476529217
        绿豆粥与茶叶蛋:可以尝试设置不同Index的返回内容,试一下,我也没测试:如if (index >= pageSum+1) {
        return nil;
        }
      • 今天中午吃什么:感谢分享,以后可能会用到:blush:
      • 萧城x:之前只知道webview 没想到还有3中 笔者应该讲讲 哪种最好
        绿豆粥与茶叶蛋:@Jason_cjc 恩 后面有时间加上:monkey_face:
      • 十字云:还不错
      • 1a83a196ac73:core graphic 的坐标系和UIkit的坐标系不一样嘛?在iPhone上
        1a83a196ac73:@绿豆粥与茶叶蛋 是的,对象搞错l
        绿豆粥与茶叶蛋:不一样的,UIKit y轴向下,Core Graphics(Quartz) y轴向上的。
      • jh1990:我就用的webview一种,学习了
        绿豆粥与茶叶蛋:@jh1990 本文没有涉及到pdf的标注只是显示和预览,像UIWebview,QLPreviewController的方式包含文字的copy,如果相对pdf中的内容做标注可以考虑CGContext分层画画,结合触摸事件API touchesBegan: withEvent: 和touchesMoved: withEvent: 以及路径CGMutablePathRef或者UIBezierPath来试试。
        jh1990:@绿豆粥与茶叶蛋 你的项目中可以对pd中的内容做标注么
      • 拒绝长大的萝卜丝:有Demo吗?
        绿豆粥与茶叶蛋:文章所涉及的Demo已上传至github,请看结语处有链接。
      • 我在鄱阳湖边:收藏,但是不知道什么时候才会看
        绿豆粥与茶叶蛋:@我在鄱阳湖边 慢慢看:blush:

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

      本文链接:https://www.haomeiwen.com/subject/wecsattx.html