Creating, printing and emailing high resolution PDF’s in iOS

Many kinds of business software require some form of PDF output. The easiest way in iOS to create PDF’s is using Interface Builder and the UIKit.

Generate a PDF using UIKit

First, create a blank new empty user interface file in XCode. I’ll assume you called it PDFView.xib. Make whichever view controller will be generating the PDF the owner.

Add the main pdf view, and make it 612×792, which Apple says is the standard size for a full 8.5×11 sheet of paper. (You’ll need it a standard size if you want to print, since AirPrint doesn’t allow you to shrink larger pdf’s to fit, instead cropping off the sides that are too big or printing the page tiled across multiple pages.) Assign the main view to an UIView IBOutlet in your owner view controller; I’ll assume its called PDFView1.

In the main pdf view, lay out all your UI elements as UIViews (to group), UIImageViews, and UILabels. You can add other UI elements if you want, but most standard documents can be created with only those two.

You can add more views to the file if you want to add more pages to your PDF.

When you want to create your PDF, you would do something like this:

- (NSData*)generatePDF {
   NSMutableData * pdfData=[NSMutableData data];

   [[NSBundle mainBundle] loadNibNamed:@"PDFView" owner:self options:nil];

   // by default, the UIKit will create a 612x792 page size (8.5 x 11 inches)
   // if you pass in CGRectZero for the size
   UIGraphicsBeginPDFContextToData(pdfData, CGRectZero,nil);
   CGContextRef pdfContext=UIGraphicsGetCurrentContext();

   // repeat the code between the lines for each pdf page you want to output
   // ======================================================================
   UIGraphicsBeginPDFPage();

   // add code to update the UI elements in the first page here
	
   // use the currently being outputed view's layer here	
   [self.PDFView1.layer renderInContext:pdfContext];

   // end repeat code
   // ======================================================================

   // finally end the PDF context.
   UIGraphicsEndPDFContext();

   // and return the PDF data.
   return pdfData;
}

Pretty simple eh?

Saving/Printing/Emailing the PDF.

Once you’ve generated the PDF, its easy to save/print/email.

Saving is just a matter of writing it to a file:

- (BOOL)savePDF:(NSData*)pdf toFile:(NSString*)filePath {
   if(![data writeToFile:filePath atomically:NO]){
      NSLog(@"Failed to pdf to file '%@'", filePath);
      return NO;
   }
   return YES;
}

Printing is not much harder. We simply grab the shared print controller, set up the orientation, job name, etc, set the printer’s printingItem to our PDF data, and present the printer controller to the user, which lets them choose which printer to use. iOS takes care of the rest.

- (void)printPDF:(NSData*)pdfData {
   UIPrintInteractionController *printer=[UIPrintInteractionController sharedPrintController];
   UIPrintInfo *info = [UIPrintInfo printInfo];
   info.orientation = UIPrintInfoOrientationPortrait;
   info.outputType = UIPrintInfoOutputGeneral;
   info.jobName=@"CadabraCorp.pdf";
   info.duplex=UIPrintInfoDuplexLongEdge;
   printer.printInfo = info;
   printer.showsPageRange=YES;
   printer.printingItem=pdfData;
		
   UIPrintInteractionCompletionHandler completionHandler =
      ^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
         if (!completed && error)
         NSLog(@"FAILED! error = %@",[error localizedDescription]);
      };
   [printer presentAnimated:YES completionHandler:completionHandler];
}

Emailing is similarly simple. We just create a new MFMailComposeViewController, add the data as a pdf attachment, and present it to the user to fill in email address, etc. (We could put in defaults before we present it, but I leave that to your imagination.):

- (void)emailPDF:(NSData*)pdfData {
   MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];

   picker.mailComposeDelegate = self;
   [picker setSubject:@"Sending PDF"];
   [picker addAttachmentData:pdfData mimeType:@"application/pdf" 
                    fileName:[NSString stringWithFormat:@"CadabraCorp.pdf"]];
   [picker setMessageBody:@"Here's the PDF you wanted." isHTML:YES];

   [self presentModalViewController:picker animated:YES];
   [picker release];
}

#pragma mark = MFMailComposeViewControllerDelegate function

- (void)mailComposeController:(MFMailComposeViewController*)controller 
          didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
   [self dismissModalViewControllerAnimated:YES];
   if (result==MessageComposeResultSent) {
      NSLog(@"PDF sent");
   } else {
      NSLog(@"PDF send error %@",error);
   }
}

Why does it look so pixelated?

Unfortunately, using the standard way of creating PDF’s with UIKit results in a bit of a problem… By default, UIKit uses 72dpi, and that leaves quite a bit of it looking pixelated. The only solution I’ve found is to make the View’s larger (I usually do 4x larger to make the math easy) and then shrink it when generating the PDF. This results in a higher resolution PDF that is still a full page and prints properly at 100%.

First, resize each UI element by multiplying its x/y/width/height by 4. Any fonts should have their point sizes multiplied by 4 as well. You may have to create new images for the UIImageView’s you use.

Next, modify the creation of the output PDF to scale the layer’s:

- (NSData*)generatePDF {
   NSMutableData * pdfData=[NSMutableData data];

   [[NSBundle mainBundle] loadNibNamed:@"PDFView" owner:self options:nil];

   // by default, the UIKit will create a 612x792 page size (8.5 x 11 inches)
   // if you pass in CGRectZero for the size
   UIGraphicsBeginPDFContextToData(pdfData, CGRectZero,nil);
   CGContextRef pdfContext=UIGraphicsGetCurrentContext();

   // repeat the code between the lines for each pdf page you want to output
   // ======================================================================
   UIGraphicsBeginPDFPage();

   // add code to update the UI elements in the first page here
	
   CGContextSaveGState(pdfContext);
   CGContextConcatCTM(pdfContext,CGAffineTransformMakeScale(.25, .25));

   // use the currently being outputed view's layer here	
   [self.PDFView1.layer renderInContext:pdfContext];

   CGContextRestoreGState(pdfContext);

   // end repeat code
   // ======================================================================

   // finally end the PDF context.
   UIGraphicsEndPDFContext();

   // and return the PDF data.
   return pdfData;
}

That’s it! The end result is PDF’s of exactly the same size (8.5×11) but with higher resolution text and images.

Bookmark the permalink.

4 Responses to Creating, printing and emailing high resolution PDF’s in iOS

  1. Nash says:

    Hi John,

    Thanks for the quality improvement.

    Just one question about the “pointSize” in UILabels’fonts.

    You said : “Any fonts should have their point sizes multiplied by 4 as well”

    How can I do this ?

    I tried :

    UIFontDescriptor *fontDesc = label.font.fontDescriptor;
    UIFont *font = [UIFont fontWithDescriptor:fontDesc size:label.font.pointSize * 4];
    [label setFont:font];

    But the fonts are very very big !

    Thanks again.

    • John says:

      All the resizing to 4x their original size should be done in Interface Architect, not programatically. This will result in huge views that you can’t actually see completely when developing, so you’ll have to scroll around the window to view/manipulate it all.

  2. Kat says:

    Hi John,
    I have a question. You mentions about making UIView 4 times bigger. However, it doesn’t fit in the iphone or ipad screen size. How do you make it to fit within the screen?

    • John says:

      The UIView we use to create the PDF isn’t ever displayed on-screen. Its manipulated off-screen only, so the user never sees the view itself, only the pdf that’s generated from it.

Leave a Reply

Your email address will not be published. Required fields are marked *