- Working with Uniform Type Identifiers
- Accessing the System Pasteboard
- Recipe: Passively Updating the Pasteboard
- Recipe: Enabling Document File Sharing
- Recipe: Monitoring the Documents Folder
- The Document Interaction Controller
- Recipe: Checking for the Open Menu
- Declaring Document Support
- Recipe: Implementing Document Support
- Exported Type Declarations
- Creating URL-Based Services
- Recipe: Adding Custom Settings Bundles-Or Not
- Summary
Recipe: Monitoring the Documents Folder
At some point in the future, you will be able to use a simple NSMetadataQuery monitor to watch your Documents folder and report updates. At the time this book was written, that metadata surveillance was not yet extended beyond iCloud for use with other folders. Code ported from OS X fails to work as expected on iOS. As I write, there are precisely two available search domains for iOS: the ubiquitous data scope, and the ubiquitous documents scope (i.e., iCloud and iCloud). See Chapter 18, “iCloud Basics,” for examples of using metadata queries.
Until that functionality arrives in iOS, you can still fall back to kqueue. This older technology provides scalable event notification (and, apparently, what powers Apple’s Grand Central Dispatch). With kqueue, you can monitor add and clear events in the Documents folder. This roughly equates to looking for files being added and deleted, which are the primary kinds of updates you want to react to. Recipe 16-3 presents a kqueue implementation for watching the Documents folder.
The following snippet demonstrates how you might use this watcher to update a file list in your GUI. Whenever the contents change, an update notification allows the app to refresh those directory contents listings.
- (void) contentsChanged: (NSNotification *) notification { NSString *docPath = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:docPath error:nil]; textView.text = [array componentsJoinedByString:@"\n"]; } - (void) loadView { [super loadView]; self.view.backgroundColor = [UIColor whiteColor]; // Establish a text view textView = [[UITextView alloc] initWithFrame:CGRectZero]; textView.font = [UIFont fontWithName:@"Futura" size: IS_IPAD ? 32.0f : 16.0f]; textView.delegate = self; [self.view addSubview:textView]; // Start monitoring [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentsChanged:) name:kDocumentChanged object:nil]; NSString *docPath = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; helper = [DocWatchHelper watcherForPath:docPath]; [self contentsChanged:nil]; }
This recipe is best tested by connecting a device to iTunes and adding and removing items. The device’s onboard file list should update to reflect those changes in real time.
Recipe 16-3. Using a kqueue File Monitor
#import <fcntl.h> #import <sys/event.h> #define kDocumentChanged @"DocumentsFolderContentsDidChangeNotification" @interface DocWatchHelper : NSObject { CFFileDescriptorRef kqref; CFRunLoopSourceRef rls; } @property (strong) NSString *path; + (id) watcherForPath: (NSString *) aPath; @end @implementation DocWatchHelper @synthesize path; - (void)kqueueFired { int kq; struct kevent event; struct timespec timeout = { 0, 0 }; int eventCount; kq = CFFileDescriptorGetNativeDescriptor(self->kqref); assert(kq >= 0); eventCount = kevent(kq, NULL, 0, &event, 1, &timeout); assert( (eventCount >= 0) && (eventCount < 2) ); if (eventCount == 1) [[NSNotificationCenter defaultCenter] postNotificationName:kDocumentChanged object:self]; CFFileDescriptorEnableCallBacks(self->kqref, kCFFileDescriptorReadCallBack); } static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) { DocWatchHelper *helper = (DocWatchHelper *)(__bridge id)(CFTypeRef) info; [helper kqueueFired]; } - (void) beginGeneratingDocumentNotificationsInPath: (NSString *) docPath { int dirFD; int kq; int retVal; struct kevent eventToAdd; CFFileDescriptorContext context = { 0, (void *)(__bridge CFTypeRef) self, NULL, NULL, NULL }; dirFD = open([docPath fileSystemRepresentation], O_EVTONLY); assert(dirFD >= 0); kq = kqueue(); assert(kq >= 0); eventToAdd.ident = dirFD; eventToAdd.filter = EVFILT_VNODE; eventToAdd.flags = EV_ADD | EV_CLEAR; eventToAdd.fflags = NOTE_WRITE; eventToAdd.data = 0; eventToAdd.udata = NULL; retVal = kevent(kq, &eventToAdd, 1, NULL, 0, NULL); assert(retVal == 0); self->kqref = CFFileDescriptorCreate(NULL, kq, true, KQCallback, &context); rls = CFFileDescriptorCreateRunLoopSource( NULL, self->kqref, 0); assert(rls != NULL); CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); CFFileDescriptorEnableCallBacks(self->kqref, kCFFileDescriptorReadCallBack); } - (void) dealloc { self.path = nil; CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFFileDescriptorDisableCallBacks(self->kqref, kCFFileDescriptorReadCallBack); } + (id) watcherForPath: (NSString *) aPath { DocWatchHelper *watcher = [[self alloc] init]; watcher.path = aPath; [watcher beginGeneratingDocumentNotificationsInPath:aPath]; return watcher; } @end