Ad

Our DNA is written in Swift
Jump

Rendering PDF is easier than you thought

We all know by now that Adobe is almost as evil as …, well let’s say they pioneered a couple of functionalities that where great for the longest time. One being the PDF format which is actually totally built into OSX everywhere. On OSX you’re able to print into a PDF without having to install extra software. Also iOS comes with PDF support and today we’ll look at how we can draw a PDF in a view.

Note: This article examines the native PDF rendering capabilities of iOS, which are very basic to say the least. For a commercial solution to support high-performance PDF viewing, editing, and annotations in your app, we recommend PSPDFKit.

How UIWebView sees it

The first idea you might have is to use UIWebView to display PDFs which is not difficult, just get the URL, make a NSURLRequest and pass this to a web view.

_webView = [[UIWebView alloc] initWithFrame:frame];
_webView.delegate = self;
_webView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
    UIViewAutoresizingFlexibleHeight;
 
// we don't want interaction, full size
_webView.scalesPageToFit = YES;
_webView.userInteractionEnabled = NO;
_webView.backgroundColor = [UIColor whiteColor];
 
//fileURL is an NSURL to a PDF file
[_webView loadRequest:[NSURLRequest requestWithURL:fileURL]];

But there is one drawback: you cannot really control how it will arrive on screen. For one thing, UIWebView draws a fat gray border and shadow which might somewhat mess up your UI design.

Wouldn’t it be great if you could draw the PDF somehow into a view? Yes, we can!

Drawing PDF Pages

Apple provides in CoreGraphics a whole set of functions prefixed CGPDF to deal with PDFs. The easiest method for drawing the first page of a PDF file I pieced together from the documentation like this. This is a regular view where I replaced the drawRect as follows:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
 
    // PDF might be transparent, assume white paper
    [[UIColor whiteColor] set];
    CGContextFillRect(ctx, rect);
 
    // Flip coordinates
    CGContextGetCTM(ctx);
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -rect.size.height);
 
    // url is a file URL
    CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)url);
    CGPDFPageRef page1 = CGPDFDocumentGetPage(pdf, 1);
 
    // get the rectangle of the cropped inside
    CGRect mediaRect = CGPDFPageGetBoxRect(page1, kCGPDFCropBox);
    CGContextScaleCTM(ctx, rect.size.width / mediaRect.size.width,
        rect.size.height / mediaRect.size.height);
    CGContextTranslateCTM(ctx, -mediaRect.origin.x, -mediaRect.origin.y);
 
    // draw it
    CGContextDrawPDFPage(ctx, page1);
    CGPDFDocumentRelease(pdf);
}

The part about the mediaRect is necessary because PDF pages are typically larger than what you really see on screen. There are usually some printing, color and crop marks outside of the content area. I managed to eliminate those by changing the transformation matrix of the CGContext.

The official method in the documentation is to use CGPDFPageGetDrawingTransform, but this has a catch: it won’t scale the image to be larger than 100% and instead center it on screen. So we build our own transform, ignoring the aspect ratio because we want to fill the view with that.

If the PDF is small enough to keep in memory you could also load it into an NSData object and access individual pages from it super fast. To read from data as opposed to providing an NSURL you change the code like this:

// data is an NSData object we filled with the PDF data from file before
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)data]);
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithProvider(dataProvider);
CGDataProviderRelease(dataProvider);

We’re using “toll-free bridging” to simply use our NSData instance where the function is expecting a CFDataRef, and of course if we have a method called SomethingCreate then we also have to have a SomethingRelease.

Conclusion

Having this ability to render PDF pages int any resolution gives you a great deal of flexibility. A great tool to have in your toolchest! You might even go as far as using PDFs as your main graphics container because it can contain both vector and bitmap graphics. In fact it’s the closest you can get to vector graphics on iOS.

Now please don’t go and make yet another PDF reader with this. With iBooks 1.1 soon supporting native PDF viewing that would make no sense.

Here are a couple other ideas:

  • An app that lets you keep your musical note sheets on your iPad. You could mark sections and specify their order and then choose between classical mode (one sheet per screen) or 1-pass mode, where the sections are flattened such that you don’t have to go back to repeats, but always play from left to right. Couple that with some fancy notes OCR to have the iPad play a bar before you. Maybe use audio clues on when to know to turn the page.
  • An app that lets you EDIT PDFs on the iPad, with cut/copy/paste support so that you can paste things on the iPad into a PDF-based scrapbook.
  • Make a presentation app similar to Prezi. You would load a PDF as basis and then you would record zoom levels, viewed rectangle and rotations along a user-defined path. Give presentation to external display.
  • Use a similar technique to render rich-text documents into reports that you can e-mail from your app.
  • Make an electronic version of a magazine similar to Wired, no need for Adobe’s weird Illustrator-to-App converter that makes half a GB apps.

If you’re interesting in partnering or co-developing these ideas please e-mail me.


Categories: Recipes

22 Comments »

  1. This is a handy thing to know. Thanks for sharing!

  2. Thanks – this is very useful.

    Just one question – how would you be able to zoom in/out of the PDF without losing quality (ie it enlarges the vector graphics). I just can’t seem to get it right.

  3. You have to draw the PDF at the higher resolution. Easiest would be to use a CATiledLayer with multiple resolutions and always draw the correct one.

  4. Hi, Thanks so much for this!

    Just wondering if you could point me at how to render a PDF just one one specific UIView, such that if that view is hidden the PDF won’t show or better if you can render straight to a CGImage.

  5. You can use any method that’s drawing in a drawRect to also draw into a bitmap context. Only difference: you have to transform the flip the coordinate system.

  6. THANK YOU for this Tutorial. I’ve been looking for something like this for sometime.

    Dan Uff

  7. My 5 hours of brain exploding was cured by the official method in the documentation having that glitch. I couldn’t figure out how to get > 100%! 🙂

  8. Thanks for the tutorial! I tried this but the PDF that was generated did not have selectable text or images. It was like the PDF created from an image. How can that be achieved?

  9. To get more than just the rendering you need to use a library like for example PSPDFKit.

  10. Thanks for the useful tutorial. I’ve extended your code into a UIImage category to render PDFs at any size to enable scaleable app assets.

    It’s on Github at: https://github.com/mindbrix/UIImage-PDF

  11. Hello everybody !!
    I would like to ask if is possible to add some animation to fold pages.
    If someone knows how it would be very useful to share it.

    Thank so Much

  12. Hi Thanks for sharing,its really helpful and i have a doubt if i have a pdf book a by doing page curl i need to see page one by one i really struggling can you please help me out….

  13. Hi , I ‘m used way draw but not display , who can help me? Thanks

  14. I hope it isn’t too late to say that this is an awesome article. You present a great alternative to the limitations of UIWebView. Thanks!

  15. good..but how we can load all pages of pdf in to the UIView…?

  16. Thank You for the tutorial, I learned a lot from this,
    i have a question that how to create a multipage PDF file

    because I reseaced on it and found that UIGraphicsBeginPDFPage() and UIGraphicsBeginPDFPageWithInfo()
    does the same work and I used the second method from above two methods

    But the thing is that i am getting an error as follows

    : replacing +/-infinity with -2147483648.
    Sep 26 15:16:11 myappname[2243] : replacing +/-infinity with 2147483647.
    Sep 26 15:16:11 myappname[2243] : replacing +/-infinity with -2147483648.
    Sep 26 15:16:11 myappname[2243] : replacing +/-infinity with 2147483647.
    Sep 26 15:16:11 myappname[2243] : replacing +/-infinity with -2147483648.

    so can you please tell me the solution why this errors are getting generated

  17. there are a few cocoapods out there that reference this, but it’s incorrect – (i.e. https://github.com/mindbrix/UIImage-PDF)

    since you’re not choosing not to use CGPDFPageGetDrawingTransform(), when a rotated PDF is sent through, it chokes

    perhaps an update, that would handle ALL pdfs would be nice.