Printing Headers and Footers with a Print Page Renderer

I previously covered how to add basic printing with AirPrint to an iOS app. In this post I will show how you can customise the print output by adding a custom Print Page Renderer. This is the approach you will need to take if you want to add a header and footer to the printed page.

Page Renderers

As I showed in the previous post the main object to control a print job within an iOS app is the shared instance of the UIPrintInteractionController. You can control basic print formatting options by using a print formatter which can be obtained directly from common UIView classes such as a UIWebView or UITextView. However if you want to customise the print layout or add things such as headers and footers you need to use a UIPrintPageRenderer class. The print page renderer object can have one or more print formatters associated with it, each formatter controlling the format of specific pages or ranges of pages.

In the basic printing example we obtained a UIViewPrintFormatter from the UIWebVIew and assigned it to the printFormatter property of the UIPrintInteractionController object:

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

When using a print page renderer you should no longer set the print formatter directly on the UIPrintInteractionController. Instead you assign one or more print formatters to the printFormatters property, an NSArray, of the UIPrintPageRenderer object.The print page renderer object is then assigned to the printPageRenderer property of the UIPrintInteractionController.

If all you want to do is use multiple print formatters to allow individual control of the format of specific pages in a print job you can use an instance of UIPrintPageRenderer directly. However greater control is possible if you subclass the UIPrintPageRenderer and override one or more of the following methods:

  • drawHeaderForPageAtIndex:inRect: override this method to draw the header for a page. For this method to be called you must ensure you set the headerHeight property in the subclassed UIPrintPageRenderer object.
  • drawFooterForPageAtIndex:inRect: override this method to draw the footer for a page. For this method to be called you must ensure you set the footerHeight property in the subclassed UIPrintPageRenderer object.
  • drawContentForPageAtIndex:inRect: override this method to draw custom content for the page.
  • drawPrintFormatterLforPageAtIndex: override this method to allow custom drawing such as an overlay to be mixed with the print formatter assigned to a specific page.

Creating a Generic Print Page Renderer

There are many apps with printing requirements which can be met with a basic print formatter combined with a print page renderer to add custom headers and footers. I have therefore tried to create a failry generic subclass of UIPrintPageRenderer that can be reused by many iOS apps. The interface for the subclass is defined as follows:

@interface UYLGenericPrintPageRenderer : UIPrintPageRenderer;

@property (nonatomic, copy) NSString *headerText;
@property (nonatomic, copy) NSString *footerText;

The subclass is named UYLGenericPrintPageRenderer to hopefully avoid conflicting with any classes that Apple may add to UIKit. The interface is very simple in that I have for now added just two optional properties to specify the text for the header and footer. I have also set a number of defaults for the font and padding sizes that will be used when drawing the headers and footers:

#define HEADER_FONT_SIZE 14
#define HEADER_TOP_PADDING 5
#define HEADER_BOTTOM_PADDING 10
#define HEADER_RIGHT_PADDING 5
#define HEADER_LEFT_PADDING 5

#define FOOTER_FONT_SIZE 12
#define FOOTER_TOP_PADDING 10
#define FOOTER_BOTTOM_PADDING 5
#define FOOTER_RIGHT_PADDING 5
#define FOOTER_LEFT_PADDING 5

For greater flexibility these could easily be defined as additional properties allowing them to be changed for each print job. I allow the compiler to synthesise the getter methods for the two properties but I am going to define my own setters for the header and footer text. This allows me to calculate the headerHeight and footerHeight properties of the UIPrintPageRenderer at the same time. The setter for the headerText property is shown below:

- (void)setHeaderText:(NSString *)newString {

  if (_headerText != newString) {
    [_headerText release];
    _headerText = [newString copy];


    if (_headerText) {
      UIFont *font = [UIFont fontWithName:@"Helvetica"
                                     size:HEADER_FONT_SIZE];
      CGSize size = [_headerText sizeWithFont:font];
      self.headerHeight = size.height +
                          HEADER_TOP_PADDING + 
                          HEADER_BOTTOM_PADDING;
    }
  }
}

This is very similar to a normal NSString copy setter method with the addition that we calculate the size of the header whenever we set the header text. We make the assumption that the header will always use the Helvetica font and we rely on the predefined values for the font size and top and bottom padding. I will not show the setter for the footerText as it is very similar and you can take a look for yourself in the example code.

Drawing the Header

To draw the header for a page we just need to override the drawHeaderForPageAtIndex:inRect: method in our print page renderer subclass. Since the method includes the page index you could draw different header content based on the page but in this case we simply output the header text as follows:

- (void)drawHeaderForPageAtIndex:(NSInteger)pageIndex
                          inRect:(CGRect)headerRect {

  if (self.headerText) {
    UIFont *font = [UIFont fontWithName:@"Helvetica"
                                   size:HEADER_FONT_SIZE];
    CGSize size = [self.headerText sizeWithFont:font];

    // Center Text
    CGFloat drawX = CGRectGetMaxX(headerRect)/2 - size.width/2;
    CGFloat drawY = CGRectGetMaxY(headerRect) - size.height -
                                  HEADER_BOTTOM_PADDING;
    CGPoint drawPoint = CGPointMake(drawX, drawY);
    [self.headerText drawAtPoint:drawPoint withFont:font];
  }
}

This is hopefully fairly straightforward, the headerRect passed into the method specifies the rectangle into which we should draw the header. In this case I have chosen to simply center the text in the middle of the header.

Drawing the footer is similar to drawing the header except this time we override the drawFooterForPageAtIndex:inRect: method. The code is shown below:

- (void)drawFooterForPageAtIndex:(NSInteger)pageIndex
                          inRect:(CGRect)footerRect {

  UIFont *font = [UIFont fontWithName:@"Helvetica" size:FOOTER_FONT_SIZE];
  NSString *pageNumber = [NSString stringWithFormat:@"- %d -", pageIndex+1];
  CGSize size = [pageNumber sizeWithFont:font];
  CGFloat drawX = CGRectGetMaxX(footerRect)/2 - size.width/2;
  CGFloat drawY = CGRectGetMaxY(footerRect) - size.height -
                                FOOTER_BOTTOM_PADDING;
  CGPoint drawPoint = CGPointMake(drawX, drawY);
  [pageNumber drawAtPoint:drawPoint withFont:font];


  if (self.footerText) {
    size = [self.footerText sizeWithFont:font];
    drawX = CGRectGetMaxX(footerRect) - size.width - FOOTER_RIGHT_PADDING;
    drawPoint = CGPointMake(drawX, drawY);
    [self.footerText drawAtPoint:drawPoint withFont:font];
  }
}

For the footer I use the pageIndex parameter to write the page number in the center of the footer and I add the footer text, if set, aligned to the right of the footer.

Putting it All Together

With the custom print page renderer subclass defined we just need to change our web view controller printWebView method to create an instance of the page renderer. I will not show the whole method again as the only piece that has changed is as follows:

UYLGenericPrintPageRenderer *renderer = [[UYLGenericPrintPageRenderer alloc]
                                         init];
renderer.headerText = printInfo.jobName;
renderer.footerText = @"AirPrinter - UseYourLoaf.com";

UIViewPrintFormatter *formatter = [self.webView viewPrintFormatter];
[renderer addPrintFormatter:formatter startingAtPageAtIndex:0];
pc.printPageRenderer = renderer;
[renderer release];

After creating an instance of our custom print page renderer object we set the header and footer text and then we add the print formatter for the web view to our renderer object. Note that when you add the formatter you can specify the starting page that the formatter should be applied to. This allows different formatters to be specified for different ranges of pages in the content. In this case we use the same formatter for the whole print job. Finally we set the printPageRenderer property of the UIPrintInteractionController object.

Wrapping Up

I hope these two posts on printing with iOS have shown how easy it is to add print capabilities to an iOS app. The use of a custom print page renderer increases the control you have over the format of the print output. Defining a generic print page renderer subclass which can be reused in many different iOS projects also makes it easy to quickly add print support to an App.

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