IOS提供了對各種文檔(PDF, PPT, word, txt, jpg等)的瀏覽功能,這個非常使用,因為我們難免需要app裡瀏覽各種文檔。官方的Sample code DocInteraction展示了如何在IOS瀏覽各種格式的文檔。
本Sample非常簡單,在tableview裡列出了預定的和制定目錄下的文檔列表,點擊某個文檔時,能切換view並預覽文檔內容:
從這個sample裡能學到的關鍵點是:
- 從檔案目錄中載入檔案清單
- 監控文檔目錄中檔案的變動,並即時反饋在UI上
- 預覽各種文檔內容
從檔案目錄中載入檔案清單
IOS提供了NSFileManager來訪問檔案系統,從以下代碼能很清晰地瞭解到如何通過NSFileManager查詢某個目錄路徑下所有所有檔案:
- (void)directoryDidChange:(DirectoryWatcher *)folderWatcher{[self.documentURLs removeAllObjects]; // clear out the old docs and start overNSString *documentsDirectoryPath = [self applicationDocumentsDirectory];NSArray *documentsDirectoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectoryPath error:NULL]; for (NSString* curFileName in [documentsDirectoryContents objectEnumerator]){NSString *filePath = [documentsDirectoryPath stringByAppendingPathComponent:curFileName];NSURL *fileURL = [NSURL fileURLWithPath:filePath];BOOL isDirectory; [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; // proceed to add the document URL to our list (ignore the "Inbox" folder) if (!(isDirectory && [curFileName isEqualToString: @"Inbox"])) { [self.documentURLs addObject:fileURL]; }}[self.tableView reloadData];}
在UI上展現檔案清單就是通過tableView的datasource來完成,在上一節的分享中已分析此部分,此時只需reloadData重新整理一下即可。
監控文檔目錄中檔案的變動,並即時反饋在UI上
對檔案目錄的監控本例子是藉助系統核心功能來完成,先看代碼:
- (BOOL)startMonitoringDirectory:(NSString *)dirPath{// Double initializing is not going to work...if ((dirKQRef == NULL) && (dirFD == -1) && (kq == -1)){// Open the directory we're going to watchdirFD = open([dirPath fileSystemRepresentation], O_EVTONLY);if (dirFD >= 0){// Create a kqueue for our event messages...kq = kqueue();if (kq >= 0){struct kevent eventToAdd;eventToAdd.ident = dirFD;eventToAdd.filter = EVFILT_VNODE;eventToAdd.flags = EV_ADD | EV_CLEAR;eventToAdd.fflags = NOTE_WRITE;eventToAdd.data = 0;eventToAdd.udata = NULL;int errNum = kevent(kq, &eventToAdd, 1, NULL, 0, NULL);if (errNum == 0){CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };CFRunLoopSourceRef rls;// Passing true in the third argument so CFFileDescriptorInvalidate will close kq.dirKQRef = CFFileDescriptorCreate(NULL, kq, true, KQCallback, &context);if (dirKQRef != NULL){rls = CFFileDescriptorCreateRunLoopSource(NULL, dirKQRef, 0);if (rls != NULL){CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);CFRelease(rls);CFFileDescriptorEnableCallBacks(dirKQRef, kCFFileDescriptorReadCallBack);// If everything worked, return early and bypass shutting things downreturn YES;}// Couldn't create a runloop source, invalidate and release the CFFileDescriptorRefCFFileDescriptorInvalidate(dirKQRef); CFRelease(dirKQRef);dirKQRef = NULL;}}// kq is active, but something failed, close the handle...close(kq);kq = -1;}// file handle is open, but something failed, close the handle...close(dirFD);dirFD = -1;}}return NO;}
這段代碼基本上都是對核心c函數的使用,雖然有些晦澀,但流程還是很簡單的,首先是對系統事件的監控,然後基於IOS的thread架構來callback檔案目錄變動後的回調,回調的具體內容實現在KQCallback中,此方法也非常簡單,實際上就是基於Delegate模式調用到上一段的directoryDidChange方法,繼而重新整理tableview的內容。具體的c函數的用法這裡就不多說,但值得我們學習的是如何在IOS的層面上調用底層BSD系統API。
預覽各種文檔內容
本例子中最核心的點反而是最簡單的,IOS提供了QLPreviewController,通過此controller能直接得到預覽各種文檔的view,你只需要把view塞到navigationController中即可,代碼如下:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ NSURL *fileURL; if (indexPath.section == 0) { fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:documents[indexPath.row] ofType:nil]]; } else { fileURL = [self.documentURLs objectAtIndex:indexPath.row]; } [self setupDocumentControllerWithURL:fileURL]; [self.docInteractionController presentPreviewAnimated:YES]; QLPreviewController *previewController = [[QLPreviewController alloc] init]; previewController.dataSource = self; previewController.delegate = self; // start previewing the document at the current section index previewController.currentPreviewItemIndex = indexPath.row; [[self navigationController] pushViewController:previewController animated:YES]; [previewController release];}
具體如何設定預覽的內容和行為,是在QLPreviewControllerDelegate和QLPreviewControllerDataSource中完成的,所以這裡只需要在controller中實現QLPreviewControllerDelegate和QLPreviewControllerDataSource相關方法即可:
// Returns the number of items that the preview controller should preview- (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)previewController{ NSInteger numToPreview = 0; NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; if (selectedIndexPath.section == 0) numToPreview = NUM_DOCS; else numToPreview = self.documentURLs.count; return numToPreview;}// returns the item that the preview controller should preview- (id)previewController:(QLPreviewController *)previewController previewItemAtIndex:(NSInteger)idx{ NSURL *fileURL = nil; NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow]; if (selectedIndexPath.section == 0) { fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:documents[idx] ofType:nil]]; } else { fileURL = [self.documentURLs objectAtIndex:idx]; } return fileURL;}
第一個方法是設定一組文檔瀏覽的個數,會影響到在文檔預覽中的上/下一頁,第二個方法是指定文檔預覽的檔案路徑,由此可見在IOS中預覽各種文檔是件很簡單的事兒,並且可預覽的格式非常廣,只要在mac的預覽能支援的,這裡都能支援,簡單測試後發現基本能支援所有常規文檔。