View PDF in Objective-C Cocoa on Mac OS X using PDFKit

The code below has been tested and works clean on the latest version of Mac OS X 10.5 Leopard.

Since it applies to native Mac OS X, and not iOS, porting this to work on iPhone/iPad is a bit different, however, it is overall very easy once you have the base concept and the main functionality below is the same.

Note: If you create this as a blank new project and are new to PDFKit, first make sure to add the Quartz framework to your project. (ref)

First, create a class (and corresponding header) to utilize the PDF. (CODE IS CASE SENSITIVE)

PDFImageView.h

#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>

@interface PDFImageView : NSImageView

- (void) loadFromPath: (NSString *) path;

@end

PDFImageView.m

#import "PDFImageView.h"
#import "DraggableScrollView.h"

@implementation PDFImageView

- (NSPDFImageRep *) pdfRep
{
return [[[self image] representations] lastObject];
}

- (void) loadFromPath: (NSString *)path
{
NSPDFImageRep *pdfRep;
NSImage *pdfImage;
NSRect frame;

pdfRep = [NSPDFImageRep imageRepWithContentsOfFile:path];
pdfImage = [[[NSImage alloc] init]autorelease];
[pdfImage addRepresentation: pdfRep];

frame = [pdfRep bounds];
frame.size.height *= [pdfRep pageCount];

[self setImage: pdfImage];

[super setFrame: frame];

if ([self isFlipped])
    [self scrollPoint: NSMakePoint(0,0)];
else {
    [self scrollPoint: NSMakePoint(0,frame.size.height)];
}
}

- (void) drawRect: (NSRect) rect
{
NSPDFImageRep *rep;
int pageCount, pageNumber;
NSRect onePageBounds;

[[NSColor whiteColor] set];
NSRectFill (rect);

rep = [self pdfRep];
pageCount = [rep pageCount];

for (pageNumber=0;pageNumber<pageCount;pageNumber++)
{
onePageBounds = [self rectForPage: (1+pageNumber)];
if (! NSIntersectsRect(rect, onePageBounds))
continue;

[rep setCurrentPage: pageNumber];
[rep drawInRect: onePageBounds];
}
}

- (void) mouseDown: (NSEvent *) theEvent
{
NSScrollView *scrollView;
scrollView=[self enclosingScrollView];
if ([scrollView respondsToSelector: @selector(dragDocumentWithMouseDown:)])
[(DraggableScrollView*)scrollView dragDocumentWithMouseDown: theEvent];
else {
[super mouseDown: theEvent];
}

- (void) setFrameSize: (NSSize) newSize
{
NSSize PDFsize;
float correctHeight;

PDFsize = [[self pdfRep] bounds].size;
correctHeight = [[self pdfRep] pageCount] * (PDfsize.height/PDFsize.width) * newSize.width;
correctHeight = ceil(correctHeight);

if (abs (correctHeight - newSize.height) > 3.0)
newSize.height = correctHeight;

[super setFrameSize: newSize];
}

- (BOOL) knowsPageRange: (NSRangePointer) range
{
range->location=1;
range->length=[[self pdfRep] pageCount];

return YES;
}

- (NSRect) rectForPage: (int) pageNumber
{
NSPDFImageRep *rep;
int pageCount;
NSRect result;

rep = [self pdfRep];
pageCount = [rep PageCount];

result = [rep bounds];
if (! [self isFlipped])
result = NSOffsetRect(result,0.0,(pageCount-1)*result.size.height);

if ([self isFlipped])
result = NSOffsetRect (result,0.0,(pageNumber-1)*result.size.height);
else {
result = NSOffsetRect (result,0.0,-(pageNumber-1)*result.size.height);

return result;
}
}
@end

This class is courtesy of Apple DC and used in the above PDFViewer class. You shouldn’t have to really do anything additional in this one, just plug and play. Allows user to scroll with mouse. I removed alot of comments for brevity to minimize code lines. See Apple reference at bottom for full source and additional info.

DraggableScrollView.h

#import <Cocoa/Cocoa.h>
 
@interface DraggableScrollView : NSScrollView
 
- (BOOL) dragDocumentWithMouseDown:
    (NSEvent *) theEvent;
 
@end

DraggableScrollView.m

#import "DraggableScrollView.h"
 
@implementation DraggableScrollView
 
#pragma mark PRIVATE CLASS METHODS
 
+ (NSCursor *) dragCursor
{
    static NSCursor *openHandCursor = nil;
 
    if (openHandCursor == nil)
    {
        NSImage     *image;
 
        image = [NSImage imageNamed: @"fingerCursor"];
        openHandCursor = [[NSCursor alloc] initWithImage: image
            hotSpot: NSMakePoint (8, 8)]; // guess that the center is good
    }
 
    return openHandCursor;
}
 
 
#pragma mark PRIVATE INSTANCE METHODS
 
- (BOOL) canScroll
{
    if ([[self documentView] frame].size.height > [self documentVisibleRect].size.height)
        return YES;
    if ([[self documentView] frame].size.width > [self documentVisibleRect].size.width)
        return YES;
 
    return NO;
}
 
 
#pragma mark PUBLIC INSTANCE METHODS -- OVERRIDES FROM NSScrolLView
 
- (void) tile
{
    [super tile];
 
    //  If the user can scroll right now, make our document cursor reflect that.
    if ([self canScroll])
        [self setDocumentCursor: [[self class] dragCursor]];
    else
        [self setDocumentCursor: [NSCursor arrowCursor]];
}
 
 
#pragma mark PUBLIC INSTANCE METHODS
 
//  dragDocumentWithMouseDown: -- Given a mousedown event, which should be in
//  our document view, track the mouse to let the user drag the document.
- (BOOL) dragDocumentWithMouseDown: (NSEvent *) theEvent // RETURN: YES => user dragged (not clicked)
{
    NSPoint         initialLocation;
    NSRect          visibleRect;
    BOOL            keepGoing;
    BOOL            result = NO;
 
    initialLocation = [theEvent locationInWindow];
    visibleRect = [[self documentView] visibleRect];
    keepGoing = YES;
 
    while (keepGoing)
    {
        theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
        switch ([theEvent type])
        {
            case NSLeftMouseDragged:
            {
                NSPoint newLocation;
                NSRect  newVisibleRect;
                float   xDelta, yDelta;
 
                newLocation = [theEvent locationInWindow];
                xDelta = initialLocation.x - newLocation.x;
                yDelta = initialLocation.y - newLocation.y;
 
                //  This was an amusing bug: without checking for flipped,
                //  you could drag up, and the document would sometimes move down!
                if ([[self documentView] isFlipped])
                    yDelta = -yDelta;
 
                //  If they drag MORE than one pixel, consider it a drag
                if ( (abs (xDelta) > 1) || (abs (yDelta) > 1) )
                    result = YES;
 
                newVisibleRect = NSOffsetRect (visibleRect, xDelta, yDelta);
                [[self documentView] scrollRectToVisible: newVisibleRect];
            }
            break;
 
            case NSLeftMouseUp:
                keepGoing = NO;
                break;
 
            default:
                /* Ignore any other kind of event. */
                break;
        }                               // end of switch (event type)
    }                                   // end of mouse-tracking loop
 
    return result;
}
 
@end

And now for the main class of our application which will use the above PDFImageView class and bind/map to our interface.

MainView.h

#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>

@interface MainView : NSDocument {
IBOutlet PDFView *_pdfView;
}

@end

MainView.m

#import "MainView.h"

@implementation MainView

//will load PDF from local filesystem in current path as .app is launched from
-(void)awakeFromNib{
NSFileManager *filemgr;
filemgr = [[NsFileManager alloc]init];

NSString *currentpath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingPathExtension] stringByDeletingLastPathcomponent];

//[Util MessageBox:@"currentpath":currentpath]; //SEE MY REFERENCE BELOW FOR DEBUG UTILITY CLASS

NSString *fileName = [NSString stringWithFormat:@"%@/%@/",currentpath,@"filename.pdf"];
PDFDocument *pdfDoc = [[PDFDocument alloc] initWithURL:[NSURL fileURLWithPath:fileName]];
[_pdfView setDocument: pdfDoc];
}

@end

References
Apple DC, http://developer.apple.com/library/mac/#samplecode/PDFView/Introduction/Intro.html
“Show Dialog Message Box in Objective-C Cocoa”, https://ronniediaz.com/2011/06/13/show-dialog-message-box-in-objective-c-cocoa/
Google CodeSearch (“_pdfView”), http://www.google.com/codesearch#search&q=_pdfView+lang:objectivec

Advertisement

About Ronnie Diaz

Ronnie Diaz is a software engineer and tech consultant. Ronnie started his career in front-end and back-end development for companies in ecommerce, service industries and remote education. This work transitioned from traditional desktop client-server applications through early cloud development. Software included human resource management and service technician workflows, online retail e-commerce and electronic ordering and fulfillment, IVR customer relational systems, and video streaming remote learning SCORM web applications. Hands on server experience and software performance optimization led to creation of a startup business focused on collocated data center services and continued experience with video streaming hardware and software. This led to a career in Amazon Prime Video where Ronnie is currently employed, building software and systems which stream live sports and events for millions of viewers around the world.

Posted on June 13, 2011, in Programming & Development and tagged , , , , , , , , , , , , , , , , , , , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: