iOS在APP内浏览word、pdf、图片等文件

前言:在app内查看各种文件是大部分应用都会有的需求,由于项目中做了这个功能,整合了一下写一个记录。

苹果给我们提供了以下5种方法,但是以下这三种方法都是基于在本地缓存中已经保存了此文件才可以查看,不然是打不开的。

1.UIWebVIew

  • 加载简单,只能浏览
  • 加载出来的用户交互性很差,无法做任何操作
  • 无法翻页,无法做回调

2.UIDocumentInteractionController

  • 支持浏览不同的文件类型,如XLS文件,Word文档文件,PDF文件
  • 实现代理方法
  • 支持使用弹框出现调用系统内的应用APP查看文件

3.QLPreviewController

  • 支持浏览不同的文件类型,如XLS文件,Word文档文件,PDF文件
  • 需导入QuickLook.framework框架,实现协议中的两个代理方法
  • 上下滑动支持单个文档的浏览,左右滑动支持不同文档间的切换
  • 支持苹果自带的分享打印等。

4.用drawRect的CGContextDrawPDFPage方法直接描绘出内容来

  • 节省内存

5.第三方框架vfr/Reader加载pdf文档

  • 集成了打印,分享,发邮件,预览等多种功能

获取文件方式

//本地文件
NSURL *filePath = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"xxxxxx" ofType:@"pdf"]];
//网络文件
NSURL *filePath = [NSURL URLWithString:@"https://www.tutorialspoint.com/ios/ios_tutorial.pdf"];
UIWebView加载本地或者网络pdf文档
UIWebView *webView = [[UIWebView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
webview.scalesPageToFit = YES;//使文档的显示范围适合UIWebView的bounds
NSURL *filePath = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"myHome" ofType:@"pdf"]];
NSURLRequest *request = [NSURLRequest requestWithURL: filePath];
[myWebView loadRequest:request];
UIDocumentInteractionController加载本地文档
UIDocumentInteractionController *documentInteractionController = [UIDocumentInteractionController
                                              interactionControllerWithURL:URL];
// Configure Document Interaction Controller
documentInteractionController.delegate = self;
// Preview File
//直接打开
documentInteractionController presentPreviewAnimated:YES];
//有选择view(直接打开和选择view选择其一写)
[documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];

//实现代理,继承<UIDocumentInteractionControllerDelegate>
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller{
    return self;
}
// Preview presented/dismissed on document.  Use to set up any HI underneath.
- (void)documentInteractionControllerWillBeginPreview:(UIDocumentInteractionController *)controller{
    controller.name = @"附件预览";
    NSLog(@"willBeginPreview");
}
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller{
    NSLog(@"didEndPreview");
    [self.navigationController popViewControllerAnimated:YES];
}
// Options menu presented/dismissed on document.  Use to set up any HI underneath.
- (void)documentInteractionControllerWillPresentOptionsMenu:(UIDocumentInteractionController *)controller{
    NSLog(@"willPresentOptionsMenu");
}
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller{
    NSLog(@"didDismissOptionsMenu");
}
// Open in menu presented/dismissed on document.  Use to set up any HI underneath.
- (void)documentInteractionControllerWillPresentOpenInMenu:(UIDocumentInteractionController *)controller{
    NSLog(@"willPresentOpenInMenu");
}
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller{
    NSLog(@"didDismissOpenInMenu");
    [self.navigationController popViewControllerAnimated:YES];
}
QLPreviewController加载本地文档

导入#import <QuickLook/QuickLook.h>

QLPreviewController *qlVC = [[QLPreviewController alloc]init];
qlVC.delegate = self;
qlVC.dataSource = self;
[self.navigationController pushViewController:qlVC animated:YES];

//实现代理<QLPreviewControllerDataSource,QLPreviewControllerDelegate>
#pragma mark -
- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller {
    return 1;
}
- (id <QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index {
    return self.fileURL;
}
- (void)previewControllerWillDismiss:(QLPreviewController *)controller {
    NSLog(@"previewControllerWillDismiss");
}
- (void)previewControllerDidDismiss:(QLPreviewController *)controller {
    NSLog(@"previewControllerDidDismiss");
}
- (BOOL)previewController:(QLPreviewController *)controller shouldOpenURL:(NSURL *)url forPreviewItem:(id <QLPreviewItem>)item{
    return YES;
}
- (CGRect)previewController:(QLPreviewController *)controller frameForPreviewItem:(id <QLPreviewItem>)item inSourceView:(UIView * __nullable * __nonnull)view{
    return CGRectZero;
}
QLPreviewController加载网络文档
  • 这里地址为打开就可以直接浏览
  • 先判断缓存是否存在文件
  • 存在文件直接打开(这里选择用webview打开,用其他也可以)
  • 如果不存在文件先缓存在本地,再用webview加载
NSURL *targetURL = [NSURL URLWithString:self.fileURLString];
NSString *docPath = [self documentsDirectoryPath];
NSString *pathToDownloadTo = [NSString stringWithFormat:@"%@/%@", docPath, [targetURL lastPathComponent]];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL hasDownLoad= [fileManager fileExistsAtPath:pathToDownloadTo];
if (hasDownLoad) {
    self.fileURL = [NSURL fileURLWithPath:pathToDownloadTo];
    QLPreviewController *qlVC = [[QLPreviewController alloc]init];
    qlVC.delegate = self;
    qlVC.dataSource = self;
     [self.navigationController pushViewController:qlVC animated:YES];
    } else {
     NSURL *targetURL = [NSURL URLWithString:self.fileURLString];

     NSData *fileData = [[NSData alloc] initWithContentsOfURL:targetURL];
    // Get the path to the App's Documents directory
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder
        [fileData writeToFile:[NSString stringWithFormat:@"%@/%@", documentsDirectory, [targetURL lastPathComponent]] atomically:YES];
        NSURLRequest *request = [NSURLRequest requestWithURL:targetURL];
        [openFileWebView loadRequest:request];
    }
QLPreviewController加载网络文档
  • 这里地址为打开下载后浏览
  • 先判断缓存是否存在文件
  • 存在文件直接打开(这里选择用QLPreviewController打开,用其他也可以)
  • 如果不存在文件先下载后缓存在本地,再用QLPreviewController打开本地文档
self.fileName = @"";
if (self.fileURLString.length > 0) {
       NSRange range = [self.fileURLString rangeOfString:@"fileName="];//文件名位于的位置
       //截取文件名字方便存缓存
       self.fileName = [self.fileURLString substringWithRange:NSMakeRange(range.length + range.location, self.fileURLString.length - range.length - range.location)];
        
       if ([self hasFileInApp:self.fileName]) {//存在文件,直接打开
            [self pushPreView:self.fileName];
        }else{//不存在文件名,去下载
            [self.view addSubview:_progressV];
            
            NSURL *targetURL = [NSURL URLWithString:self.fileURLString];
            NSURLRequest *request = [NSURLRequest requestWithURL:targetURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
            [NSURLConnection connectionWithRequest:request delegate:self];
        }
    }

#pragma mark - 下载
//获取到服务器响应
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    totaLen = response.expectedContentLength;
}

//获取到数据流
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    [self.fileData appendData:data];
    currentLen = self.fileData.length;
    self.progressV.progress = currentLen*1.0/totaLen;
    NSLog(@"%f",currentLen*1.0/totaLen);//在这边可以加个进度条,因为没有导入三方就没有加了
}

//数据请求
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //第一次下载完存入缓存,并打开文档
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *filePath = [docPath stringByAppendingPathComponent:self.fileName];
    BOOL success = [fileManager createFileAtPath:filePath contents:self.fileData attributes:nil];
    if (success == YES) {
        self.fileURL = [NSURL fileURLWithPath:filePath];
        
        self.fileData = [NSMutableData new];//清空存储数据,这个很重要
        dispatch_async(dispatch_get_main_queue(), ^{
            QLPreviewController *qlVC = [[QLPreviewController alloc]init];
            qlVC.delegate = self;
            qlVC.dataSource = self;
            [self.navigationController pushViewController:qlVC animated:YES];
        });
    }
}
CGContextDrawPDFPage加载本地文档
  • 这边只截取了重要部分代码,全部代码看附件
- (void)drawPDFIncontext:(CGContextRef)context
{
    CGContextTranslateCTM(context,0.0,self.frame.size.height);
    CGContextScaleCTM(context,1.0, -1.0);
    //上面两句是对环境做一个仿射变换,如果不执行上面两句那么绘制出来的PDF文件会呈倒置效果,第二句的作用是使图形呈正立显示,第一句是调整图形的位置,如不执行绘制的图形会不在视图可见范围内
    CGPDFPageRef  pageRef = CGPDFDocumentGetPage(documentRef,pageNum);//获取需要绘制的页码的数据。两个参数,第一个数传递进来的PDF资源数据,第二个是传递进来的需要显示的页码
    CGContextSaveGState(context);//记录当前绘制环境,防止多次绘画
    CGAffineTransform  pdfTransForm = CGPDFPageGetDrawingTransform(pageRef,kCGPDFCropBox,self.bounds,0,true);//创建一个仿射变换的参数给函数。第一个参数是对应页数据;第二个参数是个枚举值,我每个都试了一下,貌似没什么区别……但是网上看的资料都用的我当前这个,所以就用这个了;第三个参数,是图形绘制的区域,我设置的是当前视图整个区域,如果有需要,自然是可以修改的;第四个是旋转的度数,这里不需要旋转了,所以设置为0;第5个,传递true,会保持长宽比
    CGContextConcatCTM(context, pdfTransForm);//把创建的仿射变换参数和上下文环境联系起来
    CGContextDrawPDFPage(context, pageRef);//把得到的指定页的PDF数据绘制到视图上
    CGContextRestoreGState(context);//恢复图形状态
}

//通过地址字符串获取PDF资源
CGPDFDocumentRef test(NSString*urlString) {
    NSURL*url = [NSURL URLWithString:urlString];//将传入的字符串转化为一个NSURL地址
    CFURLRef refURL = (__bridge_retained CFURLRef)url;//将的到的NSURL转化为CFURLRefrefURL备用
    CGPDFDocumentRef document =CGPDFDocumentCreateWithURL(refURL);//通过CFURLRefrefURL获取文件内容
    CFRelease(refURL);//过河拆桥,释放使用完毕的CFURLRefrefURL,这个东西并不接受自动内存管理,所以要手动释放
    if(document) {
//        [SVProgressHUD dismiss];
        return  document;//返回获取到的数据
    }else{
//        [SVProgressHUD dismiss];
        return   NULL; //如果没获取到数据,则返回NULL,当然,你可以在这里添加一些打印日志,方便你发现问题
    }
}

//获取所有需要显示的PDF页面
- (void)getDataArrayValue
{
    size_t totalPages = CGPDFDocumentGetNumberOfPages(_docRef);//获取总页数
    self.totalPage = (int)totalPages;//给全局变量赋值
    NSMutableArray*arr = [NSMutableArray new];
    //通过循环创建需要显示的PDF页面,并把这些页面添加到数组中
    for(int i =1; i <= totalPages; i++) {
        ReaderPDFView *view = [[ReaderPDFView alloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height) documentRef: _docRef andPageNum:i];
        [arr addObject:view];
    }
    self.dataArray= arr;//给数据数组赋值
}
第三方框架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];
}

代码下载:https://github.com/valychen/OpenFile-master-
满意的话给个star哟

感谢以下文章

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

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

推荐阅读更多精彩内容