iOS SDK – Programmatically generate a PDF file

A couple things…

First, there is a bug with CoreGraphics PDF generation in iOS that results in corrupted PDFs. I know this issue exists up to and including iOS 4.1 (I haven’t tested iOS 4.2). The issue is related to fonts and only shows up if you include text in your PDF. The symptom is that, when generating the PDF, you’ll see errors in the debug console that look like this:

<Error>: can't get CIDs for glyphs for 'TimesNewRomanPSMT'

The tricky aspect is that the resulting PDF will render fine in some PDF readers, but fail to render in other places. So, if you have control over the software that will be used to open your PDF, you may be able to ignore this issue (e.g., if you only intend to display the PDF on the iPhone or Mac desktops, then you should be fine using CoreGraphics). However, if you need to create a PDF that works anywhere, then you should take a closer look at this issue. Here’s some additional info:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/15505-pdf-font-problem-cant-get-cids-glyphs.html#post97854

As a workaround, I’ve used libHaru successfully on iPhone as a replacement for CoreGraphics PDF generation. It was a little tricky getting libHaru to build with my project initially, but once I got my project setup properly, it worked fine for my needs.

Second, depending on the format/layout of your PDF, you might consider using Interface Builder to create a view that serves as a “template” for your PDF output. You would then write code to load the view, fill in any data (e.g., set text for UILabels, etc.), then render the individual elements of the view into the PDF. In other words, use IB to specify coordinates, fonts, images, etc. and write code to render various elements (e.g., UILabel, UIImageView, etc.) in a generic way so you don’t have to hard-code everything. I used this approach and it worked out great for my needs. Again, this may or may not make sense for your situation depending on the formatting/layout needs of your PDF.

EDIT: (response to 1st comment)

My implementation is part of a commercial product meaning that I can’t share the full code, but I can give a general outline:

I created a .xib file with a view and sized the view to 850 x 1100 (my PDF was targeting 8.5 x 11 inches, so this makes it easy to translate to/from design-time coordinates).

In code, I load the view:

- (UIView *)loadTemplate
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ReportTemplate" owner:self options:nil];
    for (id view in nib) {
        if ([view isKindOfClass: [UIView class]]) {
            return view;
        }
    }

    return nil;
}

I then fill in various elements. I used tags to find the appropriate elements, but you could do this other ways. Example:

UILabel *label = (UILabel *)[templateView viewWithTag:TAG_FIRST_NAME];
if (label != nil) {
    label.text = (firstName != nil) ? firstName : @"None";

Then I call a function to render the view to the PDF file. This function recursively walks the view hierarchy and renders each subview. For my project, I need to support only Label, ImageView, and View (for nested views):

- (void)addObject:(UIView *)view
{
    if (view != nil && !view.hidden) {
        if ([view isKindOfClass:[UILabel class]]) {
            [self addLabel:(UILabel *)view];
        } else if ([view isKindOfClass:[UIImageView class]]) {
            [self addImageView:(UIImageView *)view];
        } else if ([view isKindOfClass:[UIView class]]) {
            [self addContainer:view];
        }
    }
}

As an example, here’s my implementation of addImageView (HPDF_ functions are from libHaru):

- (void)addImageView:(UIImageView *)imageView
{
    NSData *pngData = UIImagePNGRepresentation(imageView.image);
    if (pngData != nil) {
        HPDF_Image image = HPDF_LoadPngImageFromMem(_pdf, [pngData bytes], [pngData length]);
        if (image != NULL) {
            CGRect destRect = [self rectToPDF:imageView.frame];

            float x = destRect.origin.x;
            float y = destRect.origin.y - destRect.size.height;
            float width = destRect.size.width;
            float height = destRect.size.height;

            HPDF_Page page = HPDF_GetCurrentPage(_pdf);
            HPDF_Page_DrawImage(page, image, x, y, width, height);
        }
    }
}

Hopefully that gives you the idea.

Leave a Comment