Printing in iOS

This post gives a quick introduction on how to print in iOS 4.2+.

There are multiple ways to interact with AirPrint, but for brevity I’ll focus on printing a single PDF document.  This actually covers a lot of cases, since anything you can draw in Quartz can be drawn into a piece of PDF data that you can then print – and the really cool part is that you can do all this with essentially the same code you’d use to draw it on screen.

To help explain things, I’ll give code snippets from a simple view controller that supports printing when the user taps a Print button.  Here’s part of the header for that class:

@class UIPrintInteractionController;

@interface MyViewController : UIViewController {
 @private
  UIButton *printButton;
  UIPrintInteractionController *printController;
}

Loading the print controller

When the view controller is loaded, we can also load the printController:

- (void)loadView {
  /* load rest of view */
  Class printControllerClass = NSClassFromString(@"UIPrintInteractionController");
  if (printControllerClass) {
    printController = [printControllerClass sharedPrintController];
    printButton = [self setupPrintButton];
  }
}

If you want to support devices with earlier versions of iOS, then you can’t just assume your app has access to printing functionality – sometimes it won’t. The most reliable way to detect this is to use NSClassFromString, as seen here, to check if a class exists at runtime. If it does, you have printing capabilities; otherwise you don’t. Just like this example presents, it’s a good idea to show or hide your printing UI (i.e. the Print button) based on whether or not the user can print.

Printing

Actual printing is done here:

- (IBAction)printTapped {  
  void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) =
  ^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
    if (!completed && error) NSLog(@"Print error: %@", error);
  };
 
  NSData *pdfData = [self generatePDFDataForPrinting];
  printController.printingItem = pdfData;
  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    [printController presentFromRect:printButton.frame inView:printButton.superview
                            animated:YES completionHandler:completionHandler];
  } else {
    [printController presentAnimated:YES completionHandler:completionHandler];
  }
}

The first four lines of this method basically declare a new function for error-handling, or anything else we want to do when the system tells us printing is either done or failed. The caret ^ notation is part of a relatively new language feature called blocks, which provide easier ways to define and pass around callback functions. This callback just logs an error if something goes wrong.

After the callback, there are a few main actions in this method: first, we generate some PDF data that will get printed in our call to  [self generatePDFDataForPrinting]. More on that in a moment. Then we tell the print controller that we want to print this PDF data, and finally we call one of the  [printController presentX] methods, which is as close to actual printing as we get. The system takes over from there, and shows the user a universal printing interface that lets them do things like decide which printer to use. Note that we call different methods between iPhones and iPads – this is because iPad’s get nice popovers, while iPhones get a full screen menu.

Generating PDF data

Here’s one way to generate your PDF data:

#define kPDFPageBounds CGRectMake(0, 0, 8.5 * 72, 11 * 72)

- (NSData *)generatePDFDataForPrinting {
  NSMutableData *pdfData = [NSMutableData data];
  UIGraphicsBeginPDFContextToData(pdfData, kPDFPageBounds, nil);
  UIGraphicsBeginPDFPage();
  CGContextRef ctx = UIGraphicsGetCurrentContext();
  [self drawStuffInContext:ctx];  // Method also usable from drawRect:.
  UIGraphicsEndPDFContext();
  return pdfData;
}

This method doesn’t do much actual work, but it sets things up so that the method  [self drawStuffInContext:] can draw everything in a CGContextRef, and doesn’t have to worry about the details of a PDF at all. In fact, you could just as easily use the same method to draw to screen, or even to a PNG or JPEG image file.

The value of the page bounds rectangle is up to you, but it’s probably a good idea to keep the aspect ratio the same as the paper you expect to print on. A common practice is to make the size of this rectangle equal to the point size of the page, using 72 points per inch – this is how I arrived at the definition for kPDFPageBounds in the sample code.

Happy printing!

Sample Xcode Project

For reference, here is a sample Xcode project based on this post.

6 Comments