Saving Previews and Thumbnails in the Document

As one approach for providing thumbnail and preview data to Quick Look, the application can store that data as part of the document data. The generator can then access it and return it to Quick Look in a call to QLThumbnailRequestSetImageWithData or QLPreviewRequestSetDataRepresentation. This approach permits a quick response time for the generator, but at the expense of a larger document file.

To illustrate how your generator might provide previews and thumbnails using this approach, the following listings show modifications to the code for the Sketch application that writes a thumbnail image as part of the document data. Listing 7-1 shows how you might define a property of the NSDocument subclass to hold the image data.

Listing 7-1  Sketch example project: adding a thumbnail property

@interface SKTDrawDocument : NSDocument {
    @private
    NSMutableArray *_graphics;
    // ...other instance variables here...
    NSData *_thumbnail;
}
// ...existing methods here...
- (NSData *)thumbnail;

Implement the thumbnail accessor method to return the thumbnail image. To the the NSDocument method that prepares the document data for writing out to a file (dataOfType:error:) are added the lines of code in Listing 7-2 indicated by the “new” labels.

Listing 7-2  Sketch example project: including the thumbnail with the document data

static NSString *SKTThumbnailImageKey = @"SketchThumbnail";                   // new
 
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
    NSData *data,;
    NSArray *graphics = [self graphics];
    NSPrintInfo *printInfo = [self printInfo];
    NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
    BOOL useTypeConformance = [workspace respondsToSelector:@selector(type:conformsToType:)];
 
    if ((useTypeConformance && [workspace type:SKTDrawDocumentNewTypeName conformsToType:typeName])
        || [typeName isEqualToString:SKTDrawDocumentOldTypeName]) {
        NSData *tiffRep;                                                     // new
        NSMutableDictionary *properties = [NSMutableDictionary dictionary];
 
        [properties setObject:[NSNumber numberWithInt:SKTDrawDocumentCurrentVersion] forKey:SKTDrawDocumentVersionKey];
        [properties setObject:[SKTGraphic propertiesWithGraphics:graphics] forKey:SKTDrawDocumentGraphicsKey];
        [properties setObject:[NSArchiver archivedDataWithRootObject:printInfo] forKey:SKTDrawDocumentPrintInfoKey];
        tiffRep = [self TIFFDataWithGraphics:graphics error:outError];        // new
        [properties setObject:tiffRep forKey:SKTThumbnailImageKey];           // new
        data = [NSPropertyListSerialization dataFromPropertyList:properties
                                                          format:NSPropertyListBinaryFormat_v1_0
                                                errorDescription:NULL];
    } else if ((useTypeConformance && [workspace type:(__bridge NSString *)kUTTypePDF conformsToType:typeName])
               || [typeName isEqualToString:NSPDFPboardType]) {
        data = [SKTRenderingView pdfDataWithGraphics:graphics];
    } else {
        NSParameterAssert((useTypeConformance && [workspace type:(__bridge NSString *)kUTTypeTIFF conformsToType:typeName])
                          || [typeName isEqualToString:NSTIFFPboardType]);
        data = [SKTRenderingView tiffDataWithGraphics:graphics error:outError];
    }
    return data;
}

In the corresponding NSDocument method for reading document data back in (readFromData:ofType:error:) “unpack” the thumbnail from the dictionary of document properties:

_thumbnail = [properties objectForKey:SKTThumbnailImageKey];

Now implementing the generator for Sketch is a simple matter of accessing the thumbnail image data and passing it to Quick Look in a call to QLThumbnailRequestSetImageWithData, as shown in Listing 7-3. (For previews, the corresponding function is QLPreviewRequestSetDataRepresentation.)

Listing 7-3  Returning the stored thumbnail image to Quick Look

OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize)
{
    @autoreleasepool {
        SKTDrawDocument* document = [[SKTDrawDocument alloc] init];
        if (![document readFromURL:(__bridge NSURL *)url
                            ofType:(__bridge NSString *)contentTypeUTI]) {
            return noErr;
        }
        if ([document respondsToSelector:@selector(thumbnail)]) {  // runtime verification
            NSData *tiffData = [document thumbnail];
            if (tiffData != nil) {
                NSDictionary *props = [NSDictionary dictionaryWithObject:@"public.tiff" forKey:(__bridge NSString *)kCGImageSourceTypeIdentifierHint];
                QLThumbnailRequestSetImageWithData(thumbnail, (__bridge CFDataRef)tiffData, (__bridge CFDictionaryRef)props);
                return noErr;
            }
        }
        NSSize canvasSize = [document canvasSize];
        CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, *(CGSize *)&canvasSize, false, NULL);
        if (cgContext) {
            NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:YES];
            if (context) {
                [document drawDocumentInContext:context];
            }
            QLThumbnailRequestFlushContext(thumbnail, cgContext);
            CFRelease(cgContext);
        }
    }
    return noErr;
}

In the call to QLThumbnailRequestSetImageWithData, the generator indicates the image format to Quick Look with the kCGImageSourceTypeIdentifierHint property. Note that this example checks whether the class of the document object implements the thumbnail accessor method (to exclude prior versions of the application) and, if so, it checks whether thumbnail data is returned. If it isn’t, it draws the thumbnail image in a Quick Look–provided graphics context.