Mac OS系统有一个很方便的功能就是文件预览,在Finder中选中一个文件,按下空格键就能够预览其中的内容。支持图片、文档、视频等类型。在iOS4.0系统中,官方SDK提供了一个QLPreviewController,使用它就可以让我们的App在iPhone/iPad中直接预览各个文件了。官方的开发文档中说明其支持的文件类型有:
- iWork文档
- 微软Office97以上版本的文档
- RTF文档
- PDF文件
- 图片文件
- 文本文件和CSV文件
使用方法也很简单,在Frameworks中添加QuickLook.framework,直接alloc出一个QLPreviewController对象,用presentModalViewController方法把它调出来即可。要指定QLPreviewController预览那个文件,只要直接实现它的代理方法previewItemAtIndex,返回一个NSURL对象即可:
- (id)previewController:(QLPreviewController *)previewController previewItemAtIndex:(NSInteger)idx
{
return [NSURL fileURLWithPath:[NSString stringWithFormat:@“%@/Documents/files/%@”, NSHomeDirectory(), [fileList objectAtIndex:currentIndex]]];
}
官方程序如下
1.
DITableViewController.h
#import <UIKit/UIKit.h>
#import <QuickLook/QuickLook.h>
#import "DirectoryWatcher.h"
@interface DITableViewController : UITableViewController <QLPreviewControllerDataSource,
QLPreviewControllerDelegate,
DirectoryWatcherDelegate,
UIDocumentInteractionControllerDelegate> {
DirectoryWatcher *docWatcher;
NSMutableArray *documentURLs;
UIDocumentInteractionController *docInteractionController;
}
@property (nonatomic, retain) DirectoryWatcher *docWatcher;
@property (nonatomic, retain) NSMutableArray *documentURLs;
@property (nonatomic, retain) UIDocumentInteractionController *docInteractionController;
@end
DITableViewController.m文件
#import "DITableViewController.h"
@interface DITableViewController (private)
- (NSString *)applicationDocumentsDirectory;
@end
static NSString* documents[] =
{ @"Text Document.txt",
@"Image Document.jpg",
@"PDF Document.pdf",
@"HTML Document.html"
};
#define NUM_DOCS 4
#define kRowHeight 58.0f
@implementation DITableViewController
@synthesize docWatcher, documentURLs, docInteractionController;
#pragma mark -
#pragma mark View Controller
- (void)setupDocumentControllerWithURL:(NSURL *)url
{
if (self.docInteractionController == nil)
{
self.docInteractionController = [UIDocumentInteractionControllerinteractionControllerWithURL:url];
self.docInteractionController.delegate = self;
}
else
{
self.docInteractionController.URL = url;
}
}
- (void)viewDidLoad {
[superviewDidLoad];
// start monitoring the document directory…
self.docWatcher = [DirectoryWatcherwatchFolderWithPath:[selfapplicationDocumentsDirectory] delegate:self];
self.documentURLs = [NSMutableArrayarray];
// scan for existing documents
[selfdirectoryDidChange:self.docWatcher];
}
- (void)viewDidUnload {
self.documentURLs = nil;
self.docWatcher = nil;
}
- (void)dealloc {
[documentURLsrelease];
[docWatcherrelease];
[super dealloc];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
returnYES;
}
#pragma mark -
#pragma mark UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0)
{
return NUM_DOCS;
}
else
{
return self.documentURLs.count;
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *title = nil;
if (section == 0)
title = @"Example Documents";
else
{
if (self.documentURLs.count > 0)
title = @"Documents folder";
}
return title;
}
- (NSString *)formattedFileSize:(unsigned long long)size
{
NSString *formattedStr = nil;
if (size == 0)
formattedStr = @"Empty";
else
if (size > 0 && size < 1024)
formattedStr = [NSString stringWithFormat:@"%qu bytes", size];
else
if (size >= 1024 && size < pow(1024, 2))
formattedStr = [NSString stringWithFormat:@"%.1f KB", (size / 1024.)];
else
if (size >= pow(1024, 2) && size < pow(1024, 3))
formattedStr = [NSString stringWithFormat:@"%.2f MB", (size / pow(1024, 2))];
else
if (size >= pow(1024, 3))
formattedStr = [NSString stringWithFormat:@"%.3f GB", (size / pow(1024, 3))];
return formattedStr;
}
// if we installed a custom UIGestureRecognizer (i.e. long-hold), then this would be called
- (void)handleLongPress:(UILongPressGestureRecognizer *)longPressGesture
{
if (longPressGesture.state == UIGestureRecognizerStateBegan)
{
NSIndexPath *cellIndexPath = [self.tableView indexPathForRowAtPoint:[longPressGesture locationInView:self.tableView]];
NSURL *fileURL;
if (cellIndexPath.section == 0)
fileURL = [NSURLfileURLWithPath:[[NSBundlemainBundle] pathForResource:documents[cellIndexPath.row] ofType:nil]];
else
fileURL = [self.documentURLs objectAtIndex:cellIndexPath.row];
self.docInteractionController.URL = fileURL;
[self.docInteractionControllerpresentOptionsMenuFromRect:longPressGesture.view.frame
inView:longPressGesture.view
animated:YES];
}
}
- (UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
cell = [[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:cellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
NSURL *fileURL;
if (indexPath.section == 0)
{
// first section is our build-in documents
fileURL = [NSURLfileURLWithPath:[[NSBundlemainBundle] pathForResource:documents[indexPath.row] ofType:nil]];
}
else
{
// second section is the contents of the Documents folder
fileURL = [self.documentURLs objectAtIndex:indexPath.row];
}
[selfsetupDocumentControllerWithURL:fileURL];
// layout the cell
cell.textLabel.text = [[fileURL path] lastPathComponent];
NSInteger iconCount = [docInteractionController.iconscount];
if (iconCount > 0)
{
cell.imageView.image = [docInteractionController.icons objectAtIndex:iconCount - 1];
}
NSError *error;
NSString *fileURLString = [self.docInteractionController.URL path];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fileURLString error:&error];
NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] intValue];
cell.detailTextLabel.text = [NSStringstringWithFormat:@"%@ - %@",
[self formattedFileSize:fileSize], docInteractionController.UTI];
// attach to our view any gesture recognizers that the UIDocumentInteractionController provides
//cell.imageView.userInteractionEnabled = YES;
//cell.contentView.gestureRecognizers = self.docInteractionController.gestureRecognizers;
//
// or
// add a custom gesture recognizer in lieu of using the canned ones
UILongPressGestureRecognizer *longPressGesture =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
[cell.imageView addGestureRecognizer:longPressGesture];
cell.imageView.userInteractionEnabled = YES; // this is by default NO, so we need to turn it on
[longPressGesture release];
return cell;
}
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
returnkRowHeight;
}
#pragma mark -
#pragma mark UITableView delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// three ways to present a preview:
// 1. Don't implement this method and simply attach the canned gestureRecognizers to the cell
//
// 2. Don't use canned gesture recognizers and simply use UIDocumentInteractionController's
// presentPreviewAnimated: to get a preview for the document associated with this cell
//
// 3. Use the QLPreviewController to give the user preview access to the document associated
// with this cell and all the other documents as well.
// for case 2 use this, allowing UIDocumentInteractionController to handle the preview:
/*
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];
*/
// for case 3 we use the QuickLook APIs directly to preview the document -
QLPreviewController *previewController = [[QLPreviewControlleralloc] init];
previewController.dataSource = self;
previewController.delegate = self;
// start previewing the document at the current section index
previewController.currentPreviewItemIndex = indexPath.row;
[[selfnavigationController] pushViewController:previewController animated:YES];
[previewController release];
}
#pragma mark -
#pragma mark UIDocumentInteractionControllerDelegate
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)interactionController
{
returnself;
}
#pragma mark -
#pragma mark 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;
}
- (void)previewControllerDidDismiss:(QLPreviewController *)controller
{
// if the preview dismissed (done button touched), use this method to post-process previews
}
// 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 = [NSURLfileURLWithPath:[[NSBundlemainBundle] pathForResource:documents[idx] ofType:nil]];
//fileURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://vr.tudou.com/v2proxy/v2.m3u8?it=5903919&st=2"]];//http://www.tudou.com/programs/view/R-FTzWszxS0/?resourceId=0_06_02_99?fr=2
}
else
{
fileURL = [self.documentURLs objectAtIndex:idx];
}
return fileURL;
}
#pragma mark -
#pragma mark File system support
- (NSString *)applicationDocumentsDirectory
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
- (void)directoryDidChange:(DirectoryWatcher *)folderWatcher {
[self.documentURLsremoveAllObjects]; // clear out the old docs and start over
NSString *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;
[[NSFileManagerdefaultManager] 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.tableViewreloadData];
}
@end
2.
DirectoryWatcher.h 文件
#import <Foundation/Foundation.h>
@classDirectoryWatcher;
@protocol DirectoryWatcherDelegate <NSObject>
@required
- (void)directoryDidChange:(DirectoryWatcher *)folderWatcher;
@end
@interface DirectoryWatcher : NSObject {
id <DirectoryWatcherDelegate> delegate;
int dirFD;
int kq;
CFFileDescriptorRef dirKQRef;
}
@property (nonatomic, assign) id <DirectoryWatcherDelegate> delegate;
+ (DirectoryWatcher *)watchFolderWithPath:(NSString *)watchPath delegate:(id<DirectoryWatcherDelegate>)watchDelegate;
- (void)invalidate;
@end
DirectoryWatcher.m 文件
#import "DirectoryWatcher.h"
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#import <CoreFoundation/CoreFoundation.h>
@interface DirectoryWatcher (DirectoryWatcherPrivate)
- (BOOL)startMonitoringDirectory:(NSString *)dirPath;
- (void)kqueueFired;
@end
#pragma mark -
@implementation DirectoryWatcher
@synthesize delegate;
- (id)init {
self= [superinit];
delegate = NULL;
dirFD = -1;
kq = -1;
dirKQRef = NULL;
returnself;
}
- (void)dealloc {
[selfinvalidate];
[superdealloc];
}
+ (DirectoryWatcher *)watchFolderWithPath:(NSString *)watchPath delegate:(id)watchDelegate {
DirectoryWatcher *retVal = NULL;
if ((watchDelegate != NULL) && (watchPath != NULL)) {
DirectoryWatcher *tempManager = [[[DirectoryWatcheralloc] init] autorelease];
tempManager.delegate = watchDelegate;
if ([tempManager startMonitoringDirectory: watchPath]) {
// Everything appears to be in order, so return the DirectoryWatcher.
// Otherwise we'll fall through and return NULL.
retVal = tempManager;
}
}
return retVal;
}
- (void)invalidate {
if (dirKQRef != NULL) {
CFFileDescriptorInvalidate(dirKQRef);
CFRelease(dirKQRef);
dirKQRef = NULL;
// We don't need to close the kq, CFFileDescriptorInvalidate closed it instead.
// Change the value so no one thinks it's still live.
kq = -1;
}
if(dirFD != -1) {
close(dirFD);
dirFD = -1;
}
}
@end
#pragma mark -
@implementation DirectoryWatcher (DirectoryWatcherPrivate)
- (void)kqueueFired {
assert(kq >= 0);
struct kevent event;
struct timespec timeout = {0, 0};
int eventCount;
eventCount = kevent(kq, NULL, 0, &event, 1, &timeout);
assert((eventCount >= 0) && (eventCount < 2));
// call our delegate of the directory change
[delegatedirectoryDidChange:self];
CFFileDescriptorEnableCallBacks(dirKQRef, kCFFileDescriptorReadCallBack);
}
static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) {
DirectoryWatcher *obj;
obj = (DirectoryWatcher *)info;
assert([obj isKindOfClass:[DirectoryWatcherclass]]);
assert(kqRef == obj->dirKQRef);
assert(callBackTypes == kCFFileDescriptorReadCallBack);
[obj kqueueFired];
}
- (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 watch
dirFD = 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 down
return YES;
}
// Couldn't create a runloop source, invalidate and release the CFFileDescriptorRef
CFFileDescriptorInvalidate(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;
}
}
returnNO;
}
@end