Basic Printing with AirPrint

Apple introduced the ability to print wirelessly with iOS 4.2 using AirPrint enabled printers. Adding basic printing functionality to an iOS app is pretty straightforward, requiring just a few lines of code. In this post I will walk through adding basic printing support to an app, this will lay the foundation for some more advanced printing in a follow up post.

One caveat is that printing is only supported on devices which support multitasking which excludes older devices such as the iPhone 3G.

Printing a Web Page

To illustrate how to add support for printing I have created a simple app that allows you to enter a URL which is then displayed in a UIWebView. I have made it a universal app so we can look at the minor differences between the iPad and iPhone/iPod touch user interfaces. I will not cover all of the code to create this app as it is very simple and you can take a look at the example code yourself. The app is created from the Navigation based app template and consists of a root view controller which displays a UITextField to allow the entry of a URL as shown below:

The UITextField delegate method in the root view controller is pretty simple as shown below. It extracts the string from the UITextField and passes it to a new instance of a view controller that contains the UIWebView. Note that for brevity there is no validation that the text string actually contains a valid URL. The new view controller is then pushed onto the navigation controller stack.

- (BOOL)textFieldShouldReturn:(UITextField *)textField {

  if (textField.text) {
    WebViewController *viewController = [[WebViewController alloc]
                       initWithNibName:@"WebViewController"
                       bundle:nil];
    viewController.query = textField.text;
    [[self navigationController] pushViewController:viewController
                                 animated:YES];
    [viewController release];
  }
  [textField resignFirstResponder];
  return YES;
}

The web view controller uses NSURLRequest to retrieve the contents of the URL and display it in a UIWebView. This is mostly achieved in the viewDidLoad method:

- (void)viewDidLoad
{
  [super viewDidLoad];
  self.title = self.query;

  NSString *escapedQuery = [self.query
            stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  NSURL *url = [[NSURL alloc] initWithString:escapedQuery];
 
  if (url) {
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [self.webView loadRequest:request];

    [request release];
    [url release];
  }
}

The web view is shown below (it looks pretty much the same on the iPhone and the iPad):

Adding a Print Button

The first thing we will do is add a button to the navigation bar that can be used by the user to trigger the print user interface. We can do this in the viewDidLoad method of the web view controller as follows (previous code omitted):

- (void)viewDidLoad {
  // some code omitted

  if ([UIPrintInteractionController isPrintingAvailable]) {
    UIBarButtonItem *barButton = [[UIBarButtonItem alloc] 
                    initWithBarButtonSystemItem:UIBarButtonSystemItemAction
                    target:self
                    action:@selector(printWebView:)];

    [self.navigationItem setRightBarButtonItem:barButton animated:NO];
    self.printButton = barButton;
    [barButton release];
  }
}

Some points to note about this code:

  • Before adding the printer button we first check if the device is able to support printing using the isPrintingAvailable class method of UIPrintInteractionController.
  • The UIPrintInteractionController class along with the other print classes we need are part of UIKit so there is no need to link additional frameworks to support printing.
  • The UIBarButtonSystemItemAction button is recommended by Apple though there is of course nothing stopping you from using a custom button if you feel the need.

Simple Printing

Before getting too clever I will show the quick and easy way to print the contents of a UIView (in this case it is a UIWebView but the technique works for all UIKit views). To print we need to implement the printWebView method which is the target of the print button. Ill step through this a few lines at a time. The first thing to do is get the shared instance of the UIPrintInteractionController object:

- (void)printWebView:(id)sender {
  UIPrintInteractionController *pc = [UIPrintInteractionController
                                      sharedPrintController];

The UIPrintInteractionController is the key controlling class for interacting with the iOS printing system. It reperesents a print job so as well as the item(s) to be printed it can contain the following:

  • Print Job Options: selected printer ID, jobName, orientation, duplex, outputType
  • Print Formatting Options: start page, page count, content insets, width and height
  • Print Page Renderers: headers, footers, custom print page layouts

The print job options are stored in an object of class UIPrintInfo. In this example I am going to set two options: the job name and the outputType. There are three possible values for the output type depending on what you are printing:

  • UIPrintInfoOutputGeneral: This is the default and should be used if you are printing a mix of text, graphics and images.
  • UIPrintInfoOutputPhoto: As the name suggests this is for printing photos. It assumes a photo sized print page such as 4x6 or A6.
  • UIPrintInfoOutputGrayscale: Used when you know the content consists of just black text.

Since in this case we are printing web pages which can be a mix of text and graphics we will use UIPrintInfoOutputGeneral and set the job name to the URL string:

UIPrintInfo *printInfo = [UIPrintInfo printInfo];
printInfo.outputType = UIPrintInfoOutputGeneral;
printInfo.jobName = self.query;
pc.printInfo = printInfo;

If you are printing something that might contain multiple pages you can allow the user to select which pages to print by showing the page range option:

pc.showsPageRange = YES;

You control the layout of a printed page using a print formatter to specify margins, which page to start printing from, etc. If you are printing a UIWebView, UITextView or MKMapView you can obtain a print formatter directly from the view using the viewPrintFormatter method:

UIViewPrintFormatter *formatter = [self.webView viewPrintFormatter];
pc.printFormatter = formatter;

The next step is optional but I think is worth doing as it will help you spot errors during development. To get back error information for a print job you need to declare a block which acts as a completion handler. The block is invoked when the print job completes so in this case I just use it to log any errors when compiling in debug mode:

UIPrintInteractionCompletionHandler completionHandler = 
^(UIPrintInteractionController *printController, BOOL completed,
                                                 NSError *error) {
  if(!completed && error){
    DLog(@"Print failed - domain: %@ error code %u", error.domain,
                                                     error.code); 
  }
};

I have posted previously on how to conditionally compile for debug builds if you are interested in the definition of DLog.

Finally we need to present the user interface for the print system to allow the user to select the printer, override any of the default options and finally print the content. This is about the only point where we need to do something different for the iPhone and iPad. In the case of the iPhone the printing options are presented to the user with a modal sheet that slides up from the bottom of the screen. In the case of the iPad you can either present a popover view from the print button on the toolbar or from some arbitrary rectangle in the view. The UI_USER_INTERFACE_IDIOM function makes it easy to implement this device specific code:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  [pc presentFromBarButtonItem:self.printButton animated:YES
             completionHandler:completionHandler];
} else {
  [pc presentAnimated:YES completionHandler:completionHandler];
}

You can see the difference between the iPhone modal sheet and the iPad popover view in the following two screenshots:

Testing using the Printer Simulator

If you had to print to a real printer each time you wanted to test printing from an application you could waste a lot of ink and paper (assuming you have an AirPrint compatibile printer). Luckily the iOS SDK ships with a printer simulator which display the printed pages using Preview. The easiest way to activate the print simulator is to run your app in the iOS Simulator and then select Open Print Simulator from the File menu. With the print simulator running on a Mac you can also print from an iOS device that is on the same network.

With the print simulator running, tapping the print button should generate the printed output in preview. The output looks the same as the contents as the web view without any page numbering, headers, footers or other custom layout. For the small amount of code that we have added to the App this is not bad. To get a little more fancy we will need to add a Print Page Renderer.

Wrapping Up

Since this is turning into a long post I will save the details on how to use a custom Print Page Renderer until the next post.

The example Xcode project for this post is now very out-dated but you can find it archived in my code examples repository: