Home » Swift » How to AirPrint multiple print formatters in one go?

How to AirPrint multiple print formatters in one go?

Posted by: admin November 30, 2017 Leave a comment

Questions:

My ultimate goal is to create multiple UIMarkupTextPrintFormatter and print them all in one print job.

For example, the first formatter contains text that fills up the first page and a little bit of the second page. Although there is still space left to print the second formatter, I want the second formatter to print on the third page. So basically, before printing another formatter, start a new page.

I cannot know how many pages one formatter will fill up at compile time because it depends on user input. Maybe 1, 2 or more.

I heard that I need to use UIPrintPageRenderer so I read its docs and tried this:

// I'm using webviews here because I don't want to write 2 pages worth of markup...
// it should be the same anyway. The two webviews display different wikipedia articles
// Also, don't judge my variable names. I'm lazy and this is just test code...
let formatter = webView.viewPrintFormatter()
let formatter1 = webView2.viewPrintFormatter()

formatter.contentInsets = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)
formatter1.contentInsets = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)

let renderer = UIPrintPageRenderer()
renderer.addPrintFormatter(formatter, startingAtPageAt: 0)
renderer.addPrintFormatter(formatter1, startingAtPageAt: formatter.pageCount)

printController.printPageRenderer = renderer
printController.present(animated: true, completionHandler: nil)

To add the second print formatter, I used formatter.pageCount as the second argument. I hoped it will add the second formatter after the last page of the first. But it didn’t. It only printed the stuff in the second formatter.

I printed formatter.pageCount and found its value is 0.

I am totally confused. How can I get how many pages a formatter will fill up?

I also tried to implement my own renderer:

class MyRenderer: UIPrintPageRenderer {
    override func drawPrintFormatter(_ printFormatter: UIPrintFormatter, forPageAt pageIndex: Int) {
        printFormatter.draw(in: self.printableRect, forPageAt: pageIndex)
    }
}

But then it just crashes when the print interaction controller showed:

Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now…

What did I do wrong in my code?

Answers:

As per doc it seems, UIPrintFormatter need all the information before it calculates number of pages to print in the format.

UIPrintFormatter publishes an interface that allows you to specify the
starting page for a print job and the margins around the printed
content; given that information plus the content, a print formatter
computes the number of pages for the print job.

So this will not happen unless UIPrintFormatter start printing the content.

Since UIPrintPageRenderer may use UIPrintFormatter it can actually get number of pages associated with formatter and all you have to do is to override numberOfPages to setup different formatter at each page.

Below is the sample code from Apple. Which does exactly same thing – attaching multiple formatter (UIMarkupTextPrintFormatter) at each pages.

It overrides numberOfPages here:

- (NSInteger)numberOfPages {
    self.printFormatters = nil;
    [self setupPrintFormatters];
    return [super numberOfPages];
}

And attach formatter to each page here:

  /*
     Iterate through the recipes setting each of their html representations into 
     a UIMarkupTextPrintFormatter and add that formatter to the printing job.
     */
    - (void)setupPrintFormatters {
        NSInteger page = 0;
        CGFloat previousFormatterMaxY = CGRectGetMinY(self.contentArea);

        for (Recipe *recipe in recipes) {
            NSString *html = recipe.htmlRepresentation;

            UIMarkupTextPrintFormatter *formatter = [[UIMarkupTextPrintFormatter alloc] initWithMarkupText:html];
            [formatterToRecipeMap setObject:recipe forKey:formatter];

            // Make room for the recipe info
            UIEdgeInsets contentInsets = UIEdgeInsetsZero;
            contentInsets.top = previousFormatterMaxY + self.recipeInfoHeight;
            if (contentInsets.top > CGRectGetMaxY(self.contentArea)) {
                // Move to the next page
                page++;
                contentInsets.top = CGRectGetMinY(self.contentArea) + self.recipeInfoHeight;
            }
            formatter.contentInsets = contentInsets;

            // Add the formatter to the renderer at the specified page
            [self addPrintFormatter:formatter startingAtPageAtIndex:page];

            page = formatter.startPage + formatter.pageCount - 1;
            previousFormatterMaxY = CGRectGetMaxY([formatter rectForPageAtIndex:page]);

        }
    }

In short it is calculating page number based on content area, content inset and then attaching formatter to the page by calling the API:

 [self addPrintFormatter:formatter startingAtPageAtIndex:page];

This way Renderer has information about formatter for each pages.
You can find complete code here

Hope it will help.