iOS开发 pdf文档显示和预览

前言

在我们的开发中,有些像电子书类型的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];
}
结语

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


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOS 设备上预览二进制流的 PDF 文件,可以使用 `wx.downloadFile()` 方法下载文件,然后使用 `wx.openDocument()` 方法打开文件。但是,由于 iOS 设备的一些限制,可能会导致无法正常预览 PDF 文件。 如果您遇到 iOS 设备无法预览 PDF 文件的问题,可以尝试以下两种方法: 1. 使用第三方组件 可以使用第三方组件来实现 PDF 文件的预览,例如 `pdf.js`。您可以将 PDF 文件转换为 `base64` 格式,然后使用 `pdf.js` 解析显示。 2. 使用 WebView 使用 `WebView` 控件来加载 PDF 文件,但是需要注意的是,iOS 设备的 `WebView` 控件并不支持直接加载二进制流的 PDF 文件,需要将二进制流转换为 `base64` 格式后再进行加载。具体实现可以参考以下代码: ``` wx.downloadFile({ url: 'your_pdf_url', success: function (res) { var filePath = res.tempFilePath; wx.getFileSystemManager().readFile({ filePath: filePath, encoding: 'base64', success: function (res) { var base64 = res.data; wx.setStorageSync('pdfBase64', base64); // 将 base64 数据存储到本地缓存中 wx.navigateTo({ url: 'webview?url=' + encodeURIComponent('data:application/pdf;base64,' + base64) // 跳转 WebView 页面 }); }, fail: function (res) { console.log(res); } }); }, fail: function (res) { console.log(res); } }); ``` 在 WebView 页面中,可以通过 `window.location.href` 来获取 PDF 文件的 `base64` 数据,并使用 `PDFObject` 或其他第三方库来显示 PDF 文件。具体实现可以参考以下代码: ``` <web-view src="{{url}}" bindmessage="onMessage"></web-view> Page({ onMessage: function (e) { var base64 = wx.getStorageSync('pdfBase64'); // 从本地缓存中获取 base64 数据 var data = JSON.parse(e.detail.data); if (data.type === 'getPdfBase64') { e.target.postMessage({ type: 'pdfBase64', data: base64 }); // 将 base64 数据传递给 WebView 页面 } } }); ``` 以上代码仅供参考,具体实现可以根据实际需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值