前言:在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哟