Reader开发(二)增加PDF阅读功能

最近任务很多很忙,所以更新博客的速度很慢。

大概上周就为Reader加了一个PDF阅读的功能,但是一直没时间写上来。昨晚找一下文件发现扩展了功能的Demo居然在文件目录下看不到任何文件,但是却显示有文件大小,而且删除的时候还显示已锁定,应该不是文件被隐藏了的问题。没有办法之下,今天下午又重新把该功能在原来未修改过的Demo上写了回来,又花了一些时间。文件备份太重要了。


PDF文件和RTF,TXT这些格式的文件不同,这种文件中显示出的是图像而不是单纯的文字(就我肤浅的看法来看),这样Text Kit这个强大的文字处理引擎似乎就派不上用场了,不过可以使用官方给出的CGPDFDocumentRef和CGPDFPageRef类以及UIView的drawRect:方法来创建PDF文件和呈现PDF视图。

跟着之前Reader开发的思路,由于PDF的阅读视图是draw出来的,而RTF和TXT的阅读视图是直接使用AttributedString的,两者思路完全不同,如果将其阅读视图塞进一个ViewController中似乎会显得很乱,所以我新建了一个PDFViewController和一个PDFView类来专门管理PDF文件的阅读。

首先是在BookList表格中如果选中了PDF文件,那么跳转的目的视图控制器不是之前的ReadingViewController,而是新的PDFViewController,代码如下:

  1. else if (indexPath.section == 2) { // pdf  
  2.         name = sPdfArray_[indexPath.row];  
  3.         PDFViewController *pdfVC = [[PDFViewController alloc] initWithPDFName:name];  
  4.         [self.navigationController pushViewController:pdfVC animated:YES];  
  5.         return;  
  6.     }  
在这里使用导航控制器push了一个PDFViewController进栈,而不是present视图控制器了。这样可以很方便地直接使用UINavigationItem的title来显示当前的阅读进度。


首先给出PDFViewController的接口部分,了解一下PDFViewController的成员结构:

  1. #import <UIKit/UIKit.h>  
  2. #import "PDFView.h"  
  3.   
  4. @interface PDFViewController : UIViewController  
  5. @property (strong, nonatomic) PDFView *curView;  // 当前PDF页面视图  
  6. @property (strong, nonatomic) PDFView *addView;  // 新的PDF页面视图  
  7. @property (strong, nonatomic) PDFView *backView; // 用于制造翻页效果的视图  
  8. @property (strong, nonatomic) UIScrollView *scrollView; // 滚动视图,用于显示完整的PDF页面  
  9. @property (retain, nonatomic) CAGradientLayer *shadow;  // 用于制造阴影效果的Layer  
  10. @property (retain, nonatomic) CAGradientLayer *margin;  // 用于制造页边效果的Layer  
  11. -(id)initWithPDFName:(NSString *)name; // 通过PDF文件名初始化  
  12. @end  
以及匿名接口部分,里面包括一些私有的成员:
  1. @interface PDFViewController ()  
  2. {  
  3.     BOOL next_;     // 是否翻向下一页  
  4.     BOOL enlarged_; // pdf视图是否被放大  
  5.     NSUInteger currentPage_; // 当前页号  
  6.     NSUInteger totalPages_;  // 总页数  
  7.     CGFloat startX_;    // 翻页手势起点的x值  
  8.     CGFloat curoffset_; // 翻页手势的位移值  
  9.     CGFloat minoffset_; // 翻页手势有效的最小位移值  
  10.     CGRect pdfRect_; // 完整的PDF页面的框架矩形  
  11.     CGRect fitRect_; // 适配后的PDF页面的框架矩形  
  12.     CGPDFDocumentRef pdfRef_;  // pdf文件  
  13.     CGPDFPageRef     pdfPage_; // pdf页面  
  14. }  
  15. @property (strong, nonatomic) UITapGestureRecognizer *doubleTap_; // 双击手势,用于查看完整的PDF页面  
  16. @property (strong, nonatomic) UIView *viewForPDF; // self.view中用于放置pdf阅读视图的子视图  
  17. @end  


来看看PDFViewController的初始化方法:

  1. #pragma mark -  
  2. #pragma mark Initialize  
  3.   
  4. /* 通过PDF文件名初始化 */  
  5. -(id)initWithPDFName:(NSString *)name {  
  6.     self = [super init];  
  7.     if (self) {  
  8.         /* 根据pdf文件路径初始化pdf阅读视图 */  
  9.         NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name]; // 获取PDF文件名的完整路径  
  10.         NSLog(@"filePath = %@", filePath); // 例如:filePath = /Users/one/Library/Application Support/iPhone Simulator/7.0/Applications/5815AD09-13F2-4C77-9CAE-ADD399E85A5E/PDFReader_i7_Demo.app/CGPDFDocument.pdf  
  11.         pdfRef_ = [self createPDFFromExistFile:filePath]; // 创建pdf文件对象  
  12.         pdfPage_ = CGPDFDocumentGetPage(pdfRef_, 1); // 创建pdf首页页面  
  13.         currentPage_ = 1; // 页号,从1开始  
  14.     }  
  15.     return self;  
  16. }  
  17.   
  18. /* 根据文件路径创建pdf文件 */  
  19. - (CGPDFDocumentRef)createPDFFromExistFile:(NSString *)aFilePath {  
  20.     CFStringRef path;  
  21.     CFURLRef url;  
  22.     CGPDFDocumentRef document;  
  23.       
  24.     path = CFStringCreateWithCString(NULL, [aFilePath UTF8String], kCFStringEncodingUTF8);  
  25.     url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, NO);  
  26.     CFRelease(path);  
  27.       
  28.     document = CGPDFDocumentCreateWithURL(url);  
  29.     CFRelease(url);  
  30.       
  31.     totalPages_ = CGPDFDocumentGetNumberOfPages(document); // 设置PDF文件总页数  
  32.     NSLog(@"totalPages = %d", totalPages_);  
  33.     if (totalPages_ == 0) { // 创建出错处理  
  34.         NSLog(@"Create Error");  
  35.         return NULL;  
  36.     }  
  37.     return document;  
  38. }  
其中initWithPDFName:方法通过createPDFFromeExistFile:方法初始化了CGPDFDocumentRef类的对象。PDF文件对象的创建基本完成。


由于在阅读PDF阅读时要通过手势的移动来实现翻页,所以这里我沿用了之前的Touches in view的思路和框架,在PDFViewController的self.view中动态添加PDF阅读视图来实现阅读功能,那么就涉及到了PDFView类的使用,先看看初始化方法:

  1. /* 初始化PDFView对象 */  
  2. - (id)initWithPDFRef:(CGPDFDocumentRef)pdfr {  
  3.     pdfRef = pdfr;  
  4.     pdfPage = CGPDFDocumentGetPage(pdfRef, 1); // 创建pdf首页页面  
  5.     self.pageIndex = 1; // 要展示的页面号,从1开始  
  6.     CGRect mediaRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);  
  7.     self = [super initWithFrame:mediaRect];  
  8.     return self;  
  9. }  
PDFView负责呈现PDF文件中的内容,PDFViewController负责控制PDFView的显示和布局。

注意PDFView是UIView类的子类,所以该类自带了一个drawRect:方法,要描绘出PDF的阅读内容,就必须要实现该方法:

  1. /* drawRect:方法,每个UIView的自带方法 */  
  2. - (void)drawRect:(CGRect)rect {  
  3.     CGContextRef context = UIGraphicsGetCurrentContext(); // 获取当前的绘图上下文  
  4.     [[UIColor whiteColor] set];  
  5.     CGContextFillRect(context, rect);  
  6.     CGContextGetCTM(context);  
  7.     CGContextScaleCTM(context, 1, -1);  
  8.     CGContextTranslateCTM(context, 0, -rect.size.height);  
  9.     pdfPage = CGPDFDocumentGetPage(pdfRef, self.pageIndex);  
  10.     CGRect mediaRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);  
  11.     CGContextScaleCTM(context, rect.size.width / mediaRect.size.width, rect.size.height / mediaRect.size.height);  
  12.     CGContextTranslateCTM(context, -mediaRect.origin.x, -mediaRect.origin.y);  
  13.     CGContextDrawPDFPage(context, pdfPage); // 绘制当前页面  
  14. }  

另外在翻页时PDFView的内容必须作出更新,可以使用setNeedsDisplay方法来实现,而该方法必须被PDFViewController调用,所以可以将其写成一个接口供其它类使用:
  1. /* 更新视图,例如翻页的时候需要更新 */  
  2. - (void)reloadView {  
  3.     [self setNeedsDisplay];  
  4. }  
接口部分:
  1. @interface PDFView : UIView  
  2. {  
  3.     CGPDFDocumentRef pdfRef; // pdf文件  
  4.     CGPDFPageRef pdfPage;    // pdf页面  
  5. }  
  6. @property (assign, nonatomic) NSUInteger pageIndex; // 页面号  
  7. - (id)initWithPDFRef:(CGPDFDocumentRef)pdfr;  
  8. - (void)reloadView;  
  9. @end  
完成PDFView的任务后,我们回到PDFViewController上来,首先当然是viewDidLoad:方法了:
  1. - (void)viewDidLoad {  
  2.     [super viewDidLoad];  
  3.       
  4.     /* 初始化参数 */  
  5.     minoffset_ = self.view.frame.size.width / 5.;  
  6.     enlarged_ = NO; // 初始的PDF视图的放大状态为NO  
  7.       
  8.       
  9.     /* 初始化视图 */  
  10.     self.navigationItem.title = [NSString stringWithFormat:@"%d / %d", currentPage_, totalPages_];  
  11.     curView  = [[PDFView alloc] initWithPDFRef:pdfRef_];  
  12.     addView  = [[PDFView alloc] initWithPDFRef:pdfRef_];  
  13.     backView = [[PDFView alloc] initWithPDFRef:pdfRef_];  
  14.     backView.pageIndex = 0;  
  15.     scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];  
  16.     viewForPDF = [[UIView alloc] initWithFrame:CGRectMake(0., 60., self.view.frame.size.width, self.view.frame.size.height - 60.0)];  
  17.       
  18.       
  19.     /* 设置PDF阅读视图的页面布局 */  
  20.     CGFloat w = curView.frame.size.width;  
  21.     CGFloat h = curView.frame.size.height;  
  22.     pdfRect_  = curView.frame;  
  23.     CGFloat scale = h / w; // PDF原视图高度和宽度的比例  
  24.     NSLog(@"w = %f", w);  
  25.     NSLog(@"h = %f", h);  
  26.     CGFloat href = self.view.frame.size.width * scale; // 经过页面适配后的高度  
  27.     CGFloat yref = (self.view.frame.size.height - 60.0 - href) / 2.; // 经过页面适配后的原点y值  
  28.     NSLog(@"href = %f", href);  
  29.     NSLog(@"yref = %f", yref);  
  30.     curView.frame = CGRectMake(0., yref, self.view.frame.size.width, href); // 设置适配后PDF视图的位置和大小  
  31.     fitRect_ = curView.frame; // 保存适配后的框架矩形  
  32.     [self.view addSubview:viewForPDF];  
  33.     [viewForPDF addSubview:curView]; // 添加页面适配后的PDF视图  
  34.       
  35.       
  36.     /* 为视图添加双击手势 */  
  37.     doubleTap_ = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(enlargePDFPage:)];  
  38.     doubleTap_.numberOfTapsRequired = 2;  
  39.     [self.view addGestureRecognizer:doubleTap_];  
  40. }  



在这里说一下设置PDF阅读视图的页面布局这一段吧,由于PDFView的drawRect:方法没有draw出最适合页面的显示,所以看到如下显示:



也就是在没有UIScrollView来呈现PDFView的情况下,我们只能看到PDF页面的部分视图(放大后的),由于我对CGContextDraw这些方法真的一点都不熟悉,所以只能通过设置PDFView的frame来解决该问题了。

首先获取初始的PDFView的视图尺寸并将其保存起来:

  1. CGFloat w = curView.frame.size.width;  
  2. CGFloat h = curView.frame.size.height;  
  3. pdfRect_  = curView.frame;  
  4. 2013-09-13 18:02:34.210 Reader_i7_Demo[2257:a0b] w = 612.000000  
  5. 2013-09-13 18:02:34.211 Reader_i7_Demo[2257:a0b] h = 792.000000  

然后通过宽高比例进行适配并将其保存起来:

  1. CGFloat scale = h / w; // PDF原视图高度和宽度的比例  
  2. NSLog(@"w = %f", w);  
  3. NSLog(@"h = %f", h);  
  4. CGFloat href = 320. * scale; // 经过页面适配后的高度  
  5. CGFloat yref = (510. - href) / 2.; // 经过页面适配后的原点y值  
  6. NSLog(@"href = %f", href);  
  7. NSLog(@"yref = %f", yref);  
  8. curView.frame = CGRectMake(0., yref, self.view.frame.size.width, href); // 设置适配后PDF视图的位置和大小  
  9. fitRect_ = curView.frame; // 保存适配后的框架矩形  

来看看适配后的页面视图:


现在另一个问题来了,文字太小,看不到完整的pdf内容(以iPhone的尺寸来看),这个时候可以在视图中添加一个双击手势来显示完整的pdf内容:

  1. /* 为视图添加双击手势 */  
  2. doubleTap_ = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(enlargePDFPage:)];  
  3. doubleTap_.numberOfTapsRequired = 2;  
  4. [self.view addGestureRecognizer:doubleTap_];  

看看响应的方法:
  1. /* 双击手势的响应方法 */  
  2. -(void)enlargePDFPage:(id)sender {  
  3.     if (enlarged_ == NO) { // 如果PDF页面未被放大  
  4.         [curView removeFromSuperview];     //首先移除当前PDF页面  
  5.         [self.view addSubview:scrollView]; // 在self.view中添加scrollView  
  6.         [scrollView addSubview:curView];   // 在scrollView上重新添加curView  
  7.         curView.frame = pdfRect_; // 设置curView的框架为原始PDF视图的框架  
  8.         scrollView.contentSize = pdfRect_.size; // 设置scrollView的内容尺寸  
  9.         enlarged_ = YES; // 设置放大状态  
  10.         self.navigationController.navigationBarHidden = YES; // 隐藏导航条  
  11.     }  
  12.     else { // 如果PDF页面已经被放大  
  13.         [scrollView removeFromSuperview]; // 移除scrollView和curView  
  14.         [viewForPDF addSubview:curView]; // 在viewForPDF子视图重新添加curView  
  15.         curView.frame = fitRect_;  
  16.         enlarged_ = NO; // 取消放大状态  
  17.         self.navigationController.navigationBarHidden = NO; // 显示导航条  
  18.     }  
  19. }  
这样一来,在双击视图后,就可以查看全屏状态下的pdf视图了:


在全屏状态下再次双击视图,又看到原来的PDFView了:



最后解决一下翻页的问题,这里我沿用了之前的方法:

  1. #pragma mark -  
  2. #pragma mark Touches in view  
  3.   
  4. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  
  5.     //记录手势起点的x值  
  6.     UITouch *touch = [touches anyObject];  
  7.     startX_        = [touch locationInView:self.view].x;  
  8. }  
  9.   
  10. -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {  
  11.     //将视图中已经存在的渐变或页边阴影去掉  
  12.     if (shadow) {  
  13.         [shadow removeFromSuperlayer];  
  14.     }  
  15.     if (margin) {  
  16.         [margin removeFromSuperlayer];  
  17.     }  
  18.       
  19.     //获取当前手势触点的x值  
  20.     UITouch *touch = [touches anyObject];  
  21.     float x        = [touch locationInView:self.view].x;  
  22.     if (x - startX_ >= 0) {  
  23.         curoffset_ = x - startX_;  
  24.     }  
  25.     else {  
  26.         curoffset_ = startX_ - x;  
  27.     }  
  28.       
  29.     // 设定翻转页面的矩形范围  
  30.     CGRect rect = self.view.bounds;  
  31.     if (x >= 160) {  
  32.         rect.size.width = (320 / x - 1) * 160;  
  33.         rect.origin.x   = x - rect.size.width;  
  34.     }  
  35.     else {  
  36.         rect.size.width = 320 - x;  
  37.         rect.origin.x   = x - rect.size.width;  
  38.     }  
  39.     int tempX           = rect.origin.x; //保存翻转页面起点的x值  
  40.     backView.frame      = rect;  
  41.       
  42.     //rect用于设定翻页时左边页面的范围  
  43.     rect = self.view.bounds;  
  44.     rect.size.width = x;  
  45.       
  46.       
  47.     // 判断手势并设定页面,制造翻页效果  
  48.     if (x - startX_ > 0) { //向右划的手势,上一页  
  49.         next_ = NO;  
  50.         if (currentPage_ == 1) {  
  51.             return// 如果是第一页则不接受手势  
  52.         }  
  53.         else {  
  54.             addView.frame = rect;  
  55.             addView.clipsToBounds = YES;  
  56.             addView.pageIndex = currentPage_ - 1;  
  57.             [addView reloadView];  
  58.               
  59.             [viewForPDF insertSubview:addView aboveSubview:curView];  
  60.               
  61.             [viewForPDF insertSubview:backView aboveSubview:addView];  
  62.         }  
  63.     }  
  64.     else { //向左划的手势,下一页  
  65.         next_ = YES;  
  66.           
  67.         if (currentPage_ == totalPages_) {  
  68.             return// 如果到达最后一页则不接受手势  
  69.         }  
  70.         else {  
  71.             curView.frame = rect;  
  72.             addView.pageIndex = currentPage_ + 1;  
  73.             addView.frame = fitRect_;  
  74.             [addView reloadView];  
  75.               
  76.             [viewForPDF insertSubview:addView belowSubview:curView];  
  77.               
  78.             [viewForPDF insertSubview:backView aboveSubview:curView];  
  79.         }  
  80.     }  
  81.       
  82.     //设定翻页时backPage视图两边的渐变阴影效果  
  83.     shadow            = [[CAGradientLayer alloc] init];  
  84.     shadow.colors     = [NSArray arrayWithObjects:  
  85.                          (id)[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.1].CGColor,  
  86.                          (id)[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.2].CGColor,  
  87.                          (id)[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.1].CGColor,  
  88.                          nil];  
  89.     rect              = self.view.bounds;  
  90.     rect.size.width   = 50;  
  91.     rect.origin.x     = x - 25;  
  92.     shadow.frame      = rect;  
  93.     shadow.startPoint = CGPointMake(0.0, 0.5);  
  94.     shadow.endPoint   = CGPointMake(1.0, 0.5);  
  95.     [self.view.layer addSublayer:shadow];  
  96.       
  97.     margin            = [[CAGradientLayer alloc] init];  
  98.     margin.colors     = [NSArray arrayWithObjects:  
  99.                          (id)[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.2].CGColor,  
  100.                          (id)[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.3].CGColor,  
  101.                          nil];  
  102.     margin.frame      = CGRectMake(tempX - 35, 0, 50, self.view.bounds.size.height);  
  103.     margin.startPoint = CGPointMake(0.0, 0.5);  
  104.     margin.endPoint   = CGPointMake(1.0, 0.5);  
  105.     [self.view.layer addSublayer:margin];  
  106. }  
  107.   
  108. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {  
  109.     // 如果是第一页并且翻向上一页  
  110.     if (currentPage_ == 1) {  
  111.         if (next_ == NO) {  
  112.             return;  
  113.         }  
  114.     }  
  115.       
  116.     // 如果是最后一页并且翻向下一页  
  117.     if (currentPage_ == totalPages_) {  
  118.         if (next_ == YES) {  
  119.             UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"注意" message:@"已经到达最后一页" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];  
  120.             [av show];  
  121.             return;  
  122.         }  
  123.     }  
  124.       
  125.     if (curoffset_ < minoffset_) {  
  126.         curView.frame = fitRect_;  
  127.         curView.pageIndex = currentPage_ ;  
  128.         [curView reloadView];  
  129.           
  130.         [addView  removeFromSuperview];  
  131.         [backView removeFromSuperview];  
  132.           
  133.         //移除阴影效果  
  134.         [shadow removeFromSuperlayer];  
  135.         [margin removeFromSuperlayer];  
  136.           
  137.         return;  
  138.     }  
  139.       
  140.     if (next_ == YES) { // 下一页  
  141.         currentPage_++;  
  142.         NSLog(@"%d / %d", currentPage_, totalPages_);  
  143.         curView.frame = fitRect_;  
  144.         curView.pageIndex = currentPage_;  
  145.         [curView reloadView];  
  146.           
  147.         self.navigationItem.title = [NSString stringWithFormat:@"%d / %d", currentPage_, totalPages_];  
  148.     }  
  149.     else { // 上一页  
  150.         currentPage_--;  
  151.         NSLog(@"%d / %d", currentPage_, totalPages_);  
  152.         curView.frame = fitRect_;  
  153.         curView.pageIndex = currentPage_;  
  154.         [curView reloadView];  
  155.           
  156.         self.navigationItem.title = [NSString stringWithFormat:@"%d / %d", currentPage_, totalPages_];  
  157.     }  
  158.       
  159.     [addView  removeFromSuperview];  
  160.     [backView removeFromSuperview];  
  161.       
  162.     //移除阴影效果  
  163.     [shadow removeFromSuperlayer];  
  164.     [margin removeFromSuperlayer];  
  165. }  

原理和Reader开发(一)中的翻页效果原理是一样的,最后还是上张程序运行的图:




至此,实现PDF阅读的基本功能已经实现,不需要考虑分页的问题,对于获取PDF上面的内容可能要用到Core Text,这样可能要用另一种思维来写,暂时到此为止吧,如果对于这方面有什么新想法我会继续改进并且更新博客的。

以上关于PDF文件阅读的代码参考了网上的一些文章,大家也可以参考一下:

http://blog.csdn.net/yiyaaixuexi/article/details/7645725

http://2015.iteye.com/blog/1333272

http://www.cnblogs.com/mainPage/archive/2010/10/22/1858666.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值