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