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,代码如下:
else if (indexPath.section == 2) { // pdf
name = sPdfArray_[indexPath.row];
PDFViewController *pdfVC = [[PDFViewController alloc] initWithPDFName:name];
[self.navigationController pushViewController:pdfVC animated:YES];
return;
}
在这里使用导航控制器push了一个PDFViewController进栈,而不是present视图控制器了。
首先给出PDFViewController的接口部分,了解一下PDFViewController的成员结构:
#import <UIKit/UIKit.h>
#import "PDFView.h" @interface PDFViewController : UIViewController
@property (strong, nonatomic) PDFView *curView; // 当前PDF页面视图
@property (strong, nonatomic) PDFView *addView; // 新的PDF页面视图
@property (strong, nonatomic) PDFView *backView; // 用于制造翻页效果的视图
@property (strong, nonatomic) UIScrollView *scrollView; // 滚动视图,用于显示完整的PDF页面
@property (retain, nonatomic) CAGradientLayer *shadow; // 用于制造阴影效果的Layer
@property (retain, nonatomic) CAGradientLayer *margin; // 用于制造页边效果的Layer
-(id)initWithPDFName:(NSString *)name; // 通过PDF文件名初始化
@end
以及匿名接口部分,里面包括一些私有的成员:
@interface PDFViewController ()
{
BOOL next_; // 是否翻向下一页
BOOL enlarged_; // pdf视图是否被放大
NSUInteger currentPage_; // 当前页号
NSUInteger totalPages_; // 总页数
CGFloat startX_; // 翻页手势起点的x值
CGFloat curoffset_; // 翻页手势的位移值
CGFloat minoffset_; // 翻页手势有效的最小位移值
CGRect pdfRect_; // 完整的PDF页面的框架矩形
CGRect fitRect_; // 适配后的PDF页面的框架矩形
CGPDFDocumentRef pdfRef_; // pdf文件
CGPDFPageRef pdfPage_; // pdf页面
}
@property (strong, nonatomic) UITapGestureRecognizer *doubleTap_; // 双击手势,用于查看完整的PDF页面
@property (strong, nonatomic) UIView *viewForPDF; // self.view中用于放置pdf阅读视图的子视图
@end
来看看PDFViewController的初始化方法:
#pragma mark -
#pragma mark Initialize /* 通过PDF文件名初始化 */
-(id)initWithPDFName:(NSString *)name {
self = [super init];
if (self) {
/* 根据pdf文件路径初始化pdf阅读视图 */
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name]; // 获取PDF文件名的完整路径
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
pdfRef_ = [self createPDFFromExistFile:filePath]; // 创建pdf文件对象
pdfPage_ = CGPDFDocumentGetPage(pdfRef_, 1); // 创建pdf首页页面
currentPage_ = 1; // 页号,从1开始
}
return self;
} /* 根据文件路径创建pdf文件 */
- (CGPDFDocumentRef)createPDFFromExistFile:(NSString *)aFilePath {
CFStringRef path;
CFURLRef url;
CGPDFDocumentRef document; path = CFStringCreateWithCString(NULL, [aFilePath UTF8String], kCFStringEncodingUTF8);
url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, NO);
CFRelease(path); document = CGPDFDocumentCreateWithURL(url);
CFRelease(url); totalPages_ = CGPDFDocumentGetNumberOfPages(document); // 设置PDF文件总页数
NSLog(@"totalPages = %d", totalPages_);
if (totalPages_ == 0) { // 创建出错处理
NSLog(@"Create Error");
return NULL;
}
return document;
}
其中initWithPDFName:方法通过createPDFFromeExistFile:方法初始化了CGPDFDocumentRef类的对象。PDF文件对象的创建基本完成。
由于在阅读PDF阅读时要通过手势的移动来实现翻页,所以这里我沿用了之前的Touches in view的思路和框架,在PDFViewController的self.view中动态添加PDF阅读视图来实现阅读功能,那么就涉及到了PDFView类的使用,先看看初始化方法:
/* 初始化PDFView对象 */
- (id)initWithPDFRef:(CGPDFDocumentRef)pdfr {
pdfRef = pdfr;
pdfPage = CGPDFDocumentGetPage(pdfRef, 1); // 创建pdf首页页面
self.pageIndex = 1; // 要展示的页面号,从1开始
CGRect mediaRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
self = [super initWithFrame:mediaRect];
return self;
}
PDFView负责呈现PDF文件中的内容,PDFViewController负责控制PDFView的显示和布局。
注意PDFView是UIView类的子类,所以该类自带了一个drawRect:方法,要描绘出PDF的阅读内容,就必须要实现该方法:
/* drawRect:方法,每个UIView的自带方法 */
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext(); // 获取当前的绘图上下文
[[UIColor whiteColor] set];
CGContextFillRect(context, rect);
CGContextGetCTM(context);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
pdfPage = CGPDFDocumentGetPage(pdfRef, self.pageIndex);
CGRect mediaRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
CGContextScaleCTM(context, rect.size.width / mediaRect.size.width, rect.size.height / mediaRect.size.height);
CGContextTranslateCTM(context, -mediaRect.origin.x, -mediaRect.origin.y);
CGContextDrawPDFPage(context, pdfPage); // 绘制当前页面
}
另外在翻页时PDFView的内容必须作出更新,可以使用setNeedsDisplay方法来实现,而该方法必须被PDFViewController调用,所以可以将其写成一个接口供其它类使用:
/* 更新视图,例如翻页的时候需要更新 */
- (void)reloadView {
[self setNeedsDisplay];
}
接口部分:
@interface PDFView : UIView
{
CGPDFDocumentRef pdfRef; // pdf文件
CGPDFPageRef pdfPage; // pdf页面
}
@property (assign, nonatomic) NSUInteger pageIndex; // 页面号
- (id)initWithPDFRef:(CGPDFDocumentRef)pdfr;
- (void)reloadView;
@end
完成PDFView的任务后,我们回到PDFViewController上来,首先当然是viewDidLoad:方法了:
- (void)viewDidLoad {
[super viewDidLoad]; /* 初始化参数 */
minoffset_ = self.view.frame.size.width / 5.;
enlarged_ = NO; // 初始的PDF视图的放大状态为NO /* 初始化视图 */
self.navigationItem.title = [NSString stringWithFormat:@"%d / %d", currentPage_, totalPages_];
curView = [[PDFView alloc] initWithPDFRef:pdfRef_];
addView = [[PDFView alloc] initWithPDFRef:pdfRef_];
backView = [[PDFView alloc] initWithPDFRef:pdfRef_];
backView.pageIndex = 0;
scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
viewForPDF = [[UIView alloc] initWithFrame:CGRectMake(0., 60., self.view.frame.size.width, self.view.frame.size.height - 60.0)]; /* 设置PDF阅读视图的页面布局 */
CGFloat w = curView.frame.size.width;
CGFloat h = curView.frame.size.height;
pdfRect_ = curView.frame;
CGFloat scale = h / w; // PDF原视图高度和宽度的比例
NSLog(@"w = %f", w);
NSLog(@"h = %f", h);
CGFloat href = self.view.frame.size.width * scale; // 经过页面适配后的高度
CGFloat yref = (self.view.frame.size.height - 60.0 - href) / 2.; // 经过页面适配后的原点y值
NSLog(@"href = %f", href);
NSLog(@"yref = %f", yref);
curView.frame = CGRectMake(0., yref, self.view.frame.size.width, href); // 设置适配后PDF视图的位置和大小
fitRect_ = curView.frame; // 保存适配后的框架矩形
[self.view addSubview:viewForPDF];
[viewForPDF addSubview:curView]; // 添加页面适配后的PDF视图 /* 为视图添加双击手势 */
doubleTap_ = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(enlargePDFPage:)];
doubleTap_.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTap_];
}
在这里说一下设置PDF阅读视图的页面布局这一段吧,由于PDFView的drawRect:方法没有draw出最适合页面的显示,所以看到如下显示:
也就是在没有UIScrollView来呈现PDFView的情况下,我们只能看到PDF页面的部分视图(放大后的),由于我对CGContextDraw这些方法真的一点都不熟悉,所以只能通过设置PDFView的frame来解决该问题了。
首先获取初始的PDFView的视图尺寸并将其保存起来:
CGFloat w = curView.frame.size.width;
CGFloat h = curView.frame.size.height;
pdfRect_ = curView.frame;
2013-09-13 18:02:34.210 Reader_i7_Demo[2257:a0b] w = 612.000000
2013-09-13 18:02:34.211 Reader_i7_Demo[2257:a0b] h = 792.000000
然后通过宽高比例进行适配并将其保存起来:
CGFloat scale = h / w; // PDF原视图高度和宽度的比例
NSLog(@"w = %f", w);
NSLog(@"h = %f", h);
CGFloat href = 320. * scale; // 经过页面适配后的高度
CGFloat yref = (510. - href) / 2.; // 经过页面适配后的原点y值
NSLog(@"href = %f", href);
NSLog(@"yref = %f", yref);
curView.frame = CGRectMake(0., yref, self.view.frame.size.width, href); // 设置适配后PDF视图的位置和大小
fitRect_ = curView.frame; // 保存适配后的框架矩形
来看看适配后的页面视图:
现在另一个问题来了,文字太小,看不到完整的pdf内容(以iPhone的尺寸来看),这个时候可以在视图中添加一个双击手势来显示完整的pdf内容:
/* 为视图添加双击手势 */
doubleTap_ = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(enlargePDFPage:)];
doubleTap_.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTap_];
看看响应的方法:
/* 双击手势的响应方法 */
-(void)enlargePDFPage:(id)sender {
if (enlarged_ == NO) { // 如果PDF页面未被放大
[curView removeFromSuperview]; //首先移除当前PDF页面
[self.view addSubview:scrollView]; // 在self.view中添加scrollView
[scrollView addSubview:curView]; // 在scrollView上重新添加curView
curView.frame = pdfRect_; // 设置curView的框架为原始PDF视图的框架
scrollView.contentSize = pdfRect_.size; // 设置scrollView的内容尺寸
enlarged_ = YES; // 设置放大状态
self.navigationController.navigationBarHidden = YES; // 隐藏导航条
}
else { // 如果PDF页面已经被放大
[scrollView removeFromSuperview]; // 移除scrollView和curView
[viewForPDF addSubview:curView]; // 在viewForPDF子视图重新添加curView
curView.frame = fitRect_;
enlarged_ = NO; // 取消放大状态
self.navigationController.navigationBarHidden = NO; // 显示导航条
}
}
这样一来,在双击视图后,就可以查看全屏状态下的pdf视图了:
在全屏状态下再次双击视图,又看到原来的PDFView了:
最后解决一下翻页的问题,这里我沿用了之前的方法:
#pragma mark -
#pragma mark Touches in view -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//记录手势起点的x值
UITouch *touch = [touches anyObject];
startX_ = [touch locationInView:self.view].x;
} -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
//将视图中已经存在的渐变或页边阴影去掉
if (shadow) {
[shadow removeFromSuperlayer];
}
if (margin) {
[margin removeFromSuperlayer];
} //获取当前手势触点的x值
UITouch *touch = [touches anyObject];
float x = [touch locationInView:self.view].x;
if (x - startX_ >= 0) {
curoffset_ = x - startX_;
}
else {
curoffset_ = startX_ - x;
} // 设定翻转页面的矩形范围
CGRect rect = self.view.bounds;
if (x >= 160) {
rect.size.width = (320 / x - 1) * 160;
rect.origin.x = x - rect.size.width;
}
else {
rect.size.width = 320 - x;
rect.origin.x = x - rect.size.width;
}
int tempX = rect.origin.x; //保存翻转页面起点的x值
backView.frame = rect; //rect用于设定翻页时左边页面的范围
rect = self.view.bounds;
rect.size.width = x; // 判断手势并设定页面,制造翻页效果
if (x - startX_ > 0) { //向右划的手势,上一页
next_ = NO;
if (currentPage_ == 1) {
return; // 如果是第一页则不接受手势
}
else {
addView.frame = rect;
addView.clipsToBounds = YES;
addView.pageIndex = currentPage_ - 1;
[addView reloadView]; [viewForPDF insertSubview:addView aboveSubview:curView]; [viewForPDF insertSubview:backView aboveSubview:addView];
}
}
else { //向左划的手势,下一页
next_ = YES; if (currentPage_ == totalPages_) {
return; // 如果到达最后一页则不接受手势
}
else {
curView.frame = rect;
addView.pageIndex = currentPage_ + 1;
addView.frame = fitRect_;
[addView reloadView]; [viewForPDF insertSubview:addView belowSubview:curView]; [viewForPDF insertSubview:backView aboveSubview:curView];
}
} //设定翻页时backPage视图两边的渐变阴影效果
shadow = [[CAGradientLayer alloc] init];
shadow.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.1].CGColor,
(id)[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.2].CGColor,
(id)[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.1].CGColor,
nil];
rect = self.view.bounds;
rect.size.width = 50;
rect.origin.x = x - 25;
shadow.frame = rect;
shadow.startPoint = CGPointMake(0.0, 0.5);
shadow.endPoint = CGPointMake(1.0, 0.5);
[self.view.layer addSublayer:shadow]; margin = [[CAGradientLayer alloc] init];
margin.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.2].CGColor,
(id)[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.3].CGColor,
nil];
margin.frame = CGRectMake(tempX - 35, 0, 50, self.view.bounds.size.height);
margin.startPoint = CGPointMake(0.0, 0.5);
margin.endPoint = CGPointMake(1.0, 0.5);
[self.view.layer addSublayer:margin];
} -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 如果是第一页并且翻向上一页
if (currentPage_ == 1) {
if (next_ == NO) {
return;
}
} // 如果是最后一页并且翻向下一页
if (currentPage_ == totalPages_) {
if (next_ == YES) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"注意" message:@"已经到达最后一页" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[av show];
return;
}
} if (curoffset_ < minoffset_) {
curView.frame = fitRect_;
curView.pageIndex = currentPage_ ;
[curView reloadView]; [addView removeFromSuperview];
[backView removeFromSuperview]; //移除阴影效果
[shadow removeFromSuperlayer];
[margin removeFromSuperlayer]; return;
} if (next_ == YES) { // 下一页
currentPage_++;
NSLog(@"%d / %d", currentPage_, totalPages_);
curView.frame = fitRect_;
curView.pageIndex = currentPage_;
[curView reloadView]; self.navigationItem.title = [NSString stringWithFormat:@"%d / %d", currentPage_, totalPages_];
}
else { // 上一页
currentPage_--;
NSLog(@"%d / %d", currentPage_, totalPages_);
curView.frame = fitRect_;
curView.pageIndex = currentPage_;
[curView reloadView]; self.navigationItem.title = [NSString stringWithFormat:@"%d / %d", currentPage_, totalPages_];
} [addView removeFromSuperview];
[backView removeFromSuperview]; //移除阴影效果
[shadow removeFromSuperlayer];
[margin removeFromSuperlayer];
}
原理和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
Reader开发(二)增加PDF阅读功能的更多相关文章
- Android原生PDF功能实现:PDF阅读、PDF页面跳转、PDF手势伸缩、PDF目录树、PDF预览缩略图
1.背景 近期,公司希望实现安卓原生端的PDF功能,要求:高效.实用. 经过两天的调研.编码,实现了一个简单Demo,如上图所示. 关于安卓原生端的PDF功能实现,技术点还是很多的,为了咱们安卓开发的 ...
- 仿酷狗音乐播放器开发日志二十七 用ole为窗体增加文件拖动功能(附源码)
转载请说明原出处,谢谢~~ 中秋到了,出去玩了几天.今天把仿酷狗程序做了收尾,已经开发完成了,下一篇博客把完结的情况说一下.在这篇博客里说一下使用OLE为窗体增加文件拖拽的功能.使用播放器,我更喜欢直 ...
- Foxit Reader(福昕PDF阅读器) v4.3.1.218 绿色专业版
软件名称:Foxit Reader(福昕PDF阅读器) v4.3.1.218 绿色专业版 软件语言: 简体中文 授权方式: 免费软件 运行环境: Win 32位/64位 软件大小: 4.40MB 图片 ...
- PDF阅读器关闭“使用手型工具阅读文章”功能
1.问题描述 某些PDF文件打开时,光标显示的手型工具里面有个箭头,一点击鼠标左键,就跳转到下一页了.给阅读带来很多不便. 2.原因 因为这类PDF文档中带有"文章"(articl ...
- 使用C#开发pdf阅读器初探(基于WPF,没有使用开源库)
前言 pdf是最流行的版式格式文件标准,已成为国际标准.pdf相关的开源软件非常多,也基本能满足日常需要了.相关商业软件更是林林总总,几乎应有尽有!似乎没必要自己再独立自主开发!但,本人基于以下考虑, ...
- Delphi XE7 用indy开发微信公众平台所有功能,可刷阅读,可刷赞,可加推广(除微支付)
http://www.cnblogs.com/devinlee/p/4565933.html Delphi XE7 用indy开发微信公众平台所有功能,可刷阅读,可刷赞,可加推广(除微支付) 关注作者 ...
- js判断ie和edge是否安装Adobe Reader PDF阅读器
ie浏览器和edge浏览器,必须用Adobe Reader PDF阅读器才可以打开pdf文件,其他现代浏览器自带pdf阅读器,无需安装. 判断ie或者edge如果安装了,就浏览pdf文件:如果没安装就 ...
- Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能。
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 Android开发之清除缓存功能实现方法,可以集成在自己的app中,增加一个新功能. 下面是一个效果图 ...
- 5款 Mac 常用PDF阅读和编辑软件推荐
PDF和Word.TXT等文档一样,都是我们最常用的文档格式,那么一款好用的浏览或编辑PDF的工具就很有必要了,今天和大家分享5款Mac上优秀的PDF阅读和编辑工具. 以下内容来自[风云社区 SCOE ...
随机推荐
- perl 安装Net::ZooKeeper
<pre name="code" class="python"><pre name="code" class=" ...
- Uva 10935 Throwing cards away I
题目意思:有N张牌,标号为1~N,且牌以叠好,从上到小就是标号1-N的牌,只要牌堆数量大于等于2的时候,就采取如下操作:将最上面的牌扔掉(即离开牌堆).刚才那张牌离开后,再将新的最上面的牌放置于牌堆最 ...
- 费用流&网络流模版
费用流模版: #include<cstdio> #include<cstring> #include<queue> using namespace std; ;// ...
- UVALive 5102 Fermat Point in Quadrangle 极角排序+找距离二维坐标4个点近期的点
题目链接:点击打开链接 题意: 给定二维坐标上的4个点 问: 找一个点使得这个点距离4个点的距离和最小 输出距离和. 思路: 若4个点不是凸4边形.则一定是端点最优. 否则就是2条对角线的交点最优,能 ...
- 跟我一起学extjs5(25--模块Form的自己定义的设计[3])
跟我一起学extjs5(25--模块Form的自己定义的设计[3]) 自己定义的Form已经能够执行了,以下改一下配置,把Form里面的FieldSet放在Tab之下.改动一下Modu ...
- django cookie
设置:auth.login(request, user) response = HttpResponseRedirect(reverse("index" ...
- Chapter 3.单一职责原则
单一职责原则:就一个类而言,应该仅有一个引起它变化的原因. 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,就等于把这些职责耦合在一起, ...
- Socket编程模式
Socket编程模式 本文主要分析了几种Socket编程的模式.主要包括基本的阻塞Socket.非阻塞Socket.I/O多路复用.其中,阻塞和非阻塞是相对于套接字来说的,而其他的模式本质上来说是基于 ...
- 打工心态废掉了很多人,包括你吗?(你把现在这家公司的业务都弄清楚、弄懂了吗?君子报仇十年不晚!不离不弃!)good
我只拿这点钱,凭什么去做那么多工作,我傻呀. 我为公司干活,公司付我一份报酬,等价交换而已,我不欠谁的. 我只要对得起这份薪水就行了,多一点我都不干,做了也白做. 工作嘛,又不是为自己干,说得过去就行 ...
- [置顶] 如何判断两个IP大小关系及是否在同一个网段中
功能点 判断某个IP地址是否合法 判断两个IP地址是否在同一个网段中 判断两个IP地址的大小关系 知识准备 IP协议 子网掩码 Java 正则表达式 基本原理 IP地址范围 0.0.0.0- 255 ...