• Making a PDF from a UIWebView

    25 February 2015
    While working on a project I needed a way to generate a pdf from some in app html to a for emailing and printing purposes. This seemed like a rather easy task, however the pdf generated was always empty. After digging around the web I kept coming back to this post onStackOverflow. A bit more searching I came across this great post by Brent Nycum which became my starting point. As Brent points out, this really isn’t the most ideal situation as the document is going to be rendered at 72 ppi. In a normal situation if you are in control of the data, for instance if you want to generate a PDF from images and text from within your app, you would use Core Graphics and Quartz. The advantage there is that you can control how the pages get split up. In this simple example the text gets cropped in an undesirable way. If your still interested read on and check out the code which is available on GitHub so feel free to use it however you want. It doesn’t matter how you generate the html, whether it’s like what I needed in my project, or if it’s from an external resource like I use in the sample. So let’s start by loading our web content
    [printContentWebView setDelegate:self];
    NSURL *url = [NSURL URLWithString:@"http://happymaau.com/2011/05/17/weekly-update-2/"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [printContentWebView loadRequest:request];
    From there, we’re going to wait till the page is finished loading using the UIWebViewDelegate method webViewDidFinishLoad. If we don’t wait, we’re back to having an empty PDF. We get the total length of the web page using a tiny bit of JavaScript and after that we set the max width and height that each PDF page is going to be. With these values we need to set the webViews frame to match the page size. If we don’t do this then the width of the content rendered will still be the 320 pixels wide from the way the view was created in Interface Builder. If our UIWebView wasn’t something the user could see, we could set these values when the view was created which is what Brent does in his example.
    // Store off the original frame so we can reset it when we're done
    CGRect origframe = webView.frame;
    NSString *heightStr = [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; // Get the height of our webView
    int height = [heightStr intValue];
    // Size of the view in the pdf page
    CGFloat maxHeight  = kDefaultPageHeight - 2*kMargin;
    CGFloat maxWidth  = kDefaultPageWidth - 2*kMargin;
    int pages = ceil(height / maxHeight);
    [webView setFrame:CGRectMake(0.f, 0.f, maxWidth, maxHeight)];
    Now that the view setup is done, time to actually generate the PDF. The drawing process is rather boilerplate and similar to drawing in a graphics image context (UIGraphicsBeginImageContext) as it’s wrapped with begin and end states. There’s another minor change to Brent’s original code within the loop. Rather than call UIGraphicsBeginPDFPage, I wanted to specify the size of each page I was about to draw. This will set the media box for the PDF page and define the rect we will be drawing into. Only thing left to do is to draw the portion of the web page for each page of the PDF.
    // Set up we the pdf we're going to be generating is
    UIGraphicsBeginPDFContextToFile(self.pdfPath, CGRectZero, nil);
    int i = 0;
    for ( ; i < pages; i++)
    if (maxHeight * (i+1) > height)
    { // Check to see if page draws more than the height of the UIWebView
    CGRect f = [webView frame];
    f.size.height -= (((i+1) * maxHeight) - height);
    [webView setFrame: f];
    // Specify the size of the pdf page
    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth, kDefaultPageHeight), nil);
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    // Move the context for the margins
    CGContextTranslateCTM(currentContext, kMargin, kMargin);
    // offset the webview content so we're drawing the part of the webview for the current page
    [[[webView subviews] lastObject] setContentOffset:CGPointMake(0, maxHeight * i) animated:NO];
    // draw the layer to the pdf, ignore the "renderInContext not found" warning.
    [webView.layer renderInContext:currentContext];
    // all done with making the pdf
    // Restore the webview and move it to the top.
    [webView setFrame:origframe];
    [[[webView subviews] lastObject] setContentOffset:CGPointMake(0, 0) animated:NO];
    The process is very simple and in the code sample on GitHub I show how to use QuickLook for previewing the PDF as well as how to attach it to an email. Not the best solution for the problem, due to the content cut off issue and it being 72 ppi, but if you need a quick way to generate a PDF it’s not a bad one.


Comments closed on this post.