6

I have this method here that takes my UIWebView and convert into a PDF and its working well. But when I print off this PDF or email it, its cut off. Its like its only generating what the size of the UIWebView that I set (which is width: 688 & height: 577) If I increase the size of the UIWebView to lets say 900 or 1024 my PDF is empty. My UIWebView is bigger than 577, but in my app, I am able to scroll.

Here is method....

-(void)webViewDidFinishLoad:(UIWebView *)webViewPDF
{
    CGRect origframe = webViewPDF.frame;
    NSString *heightStr = [webViewPDF stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; // Get the height of our webView
    int height = [heightStr intValue];

    CGFloat maxHeight   = kDefaultPageHeight - 2*kMargin;
    int pages = floor(height / maxHeight);

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [paths objectAtIndex:0];

    self.pdfPath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"Purchase Order.pdf"]];

    UIGraphicsBeginPDFContextToFile(self.pdfPath, CGRectZero, nil);

    for (int i = 0; i < pages; i++)
    {
        if (maxHeight * (i+1) > height) {
            CGRect f = [webViewPDF frame];
            f.size.height -= (((i+1) * maxHeight) - height);
            [webViewPDF setFrame: f];
        }

        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth, kDefaultPageHeight), nil);
        CGContextRef currentContext = UIGraphicsGetCurrentContext();

        CGContextTranslateCTM(currentContext, kMargin, kMargin);

        [webViewPDF.layer renderInContext:currentContext];
    }

    UIGraphicsEndPDFContext();

    [webViewPDF setFrame:origframe];
    [[[webViewPDF subviews] lastObject] setContentOffset:CGPointMake(0, 0) animated:NO];
}

I hope this makes sense....Does anyone have any suggestions on how to fix this, so the PDF is not cut off?

I forgot to mention these variables:

#define kDefaultPageHeight 850
#define kDefaultPageWidth  850
#define kMargin 50

Here is my share button:

- (IBAction)Share:(id)sender {

    NSData *pdfData = [NSData dataWithContentsOfFile:self.pdfPath];

    UIActivityViewController * activityController = [[UIActivityViewController alloc] initWithActivityItems:@[pdfData] applicationActivities:nil];

    UIPopoverController *popup = [[UIPopoverController alloc] initWithContentViewController:activityController];

    [popup presentPopoverFromRect:CGRectMake(self.view.frame.size.width - 36, 60, 0, 0)inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];

}
user979331
  • 11,039
  • 73
  • 223
  • 418
  • So what does this have to do with Objective-C? – matt May 12 '15 at 21:47
  • this method is Objective-C – user979331 May 12 '15 at 21:54
  • Obviously your code happens to be in Objective-C. Anyone can see that. But the question is not _about_ Objective-C. Whatever the answer is would be the same whether you were using Objective-C, Swift, AppleScript, or whatever. – matt May 13 '15 at 01:42
  • 1
    Have you tried setting the bounds of the UIWebview to the size of the page in PDF, then scrolling the UIWebview so it shows the exact fragment you need for that page before calling layer.renderInContext? UIWebview likely optimises drawing in so that it only draws to its layer the part of the content that is visible at any given time. – David van Driessche May 14 '15 at 13:19
  • I don't understand @DavidvanDriessche, can you put in an answer? – user979331 May 14 '15 at 13:47
  • Anybody ? Please help. – user979331 May 18 '15 at 02:19

2 Answers2

5

I've done this in the past using UIPrintPageRenderer. It's a more versetile way of creating a PDF from a UIWebView, and it's been working well for me so far. I've tested this solution with Xcode 6 and iOS 8.2. Also, tried printing the resulting PDF and everything printed out fine.

When I read the OP, I did some testing with various page sizes, to see if I can get a blank PDF. There are a few key items that I identified, that could contribute to a blank PDF file. I've identified them in the code.

When webViewDidFinishLoad() gets called, the view might not be 100% loaded. A check is necessary, to see if the view is still loading. This is important, as it might be the source of your problem. If it's not, then we are good to go. There is a very important note here. Some web pages are loaded dynamically (defined in the page itself). Take youtube.com for example. The page displays almost immediately, with a "loading" screen. This will trick our web view, and it's "isLoading" property will be set to "false", while the web page is still loading content dynamically. This is a pretty rare case though, and in the general case this solution will work well. If you need to generate a PDF file from such a dynamic loading web page, you might need to move the actual generation to a different spot. Even with a dynamic loading web page, you will end up with a PDF showing the loading screen, and not an empty PDF file.

Another key aspect is setting the printableRect and pageRect. Note that those are set separately. If the printableRect is smaller than the paperRect, you will end up with some padding around the content - see code for example. Here is a link to Apple's API doc with some short descriptions for both.

The example code below adds a Category to UIPrintPageRenderer to create the actual PDF data. The code in this sample has been put together using various resources online in the past, and I wasn't able to find which ones were used to credit them properly.

@interface UIPrintPageRenderer (PDF)
- (NSData*) createPDF;
@end

@implementation UIPrintPageRenderer (PDF)
- (NSData*) createPDF
{
    NSMutableData *pdfData = [NSMutableData data];
    UIGraphicsBeginPDFContextToData( pdfData, self.paperRect, nil );
    [self prepareForDrawingPages: NSMakeRange(0, self.numberOfPages)];
    CGRect bounds = UIGraphicsGetPDFContextBounds();
    for ( int i = 0 ; i < self.numberOfPages ; i++ )
    {
        UIGraphicsBeginPDFPage();
        [self drawPageAtIndex: i inRect: bounds];
    }
    UIGraphicsEndPDFContext();
    return pdfData;
}
@end

And here is what I have in webViewDidFinishLoad()

- (void)webViewDidFinishLoad:(UIWebView *)webViewIn {
    NSLog(@"web view did finish loading");

    // webViewDidFinishLoad() could get called multiple times before
    // the page is 100% loaded. That's why we check if the page is still loading
    if (webViewIn.isLoading)
        return;

    UIPrintPageRenderer *render = [[UIPrintPageRenderer alloc] init];
    [render addPrintFormatter:webViewIn.viewPrintFormatter startingAtPageAtIndex:0];

    // Padding is desirable, but optional
    float padding = 10.0f;

    // Define the printableRect and paperRect
    // If the printableRect defines the printable area of the page
    CGRect paperRect = CGRectMake(0, 0, PDFSize.width, PDFSize.height);
    CGRect printableRect = CGRectMake(padding, padding, PDFSize.width-(padding * 2), PDFSize.height-(padding * 2));

    [render setValue:[NSValue valueWithCGRect:paperRect] forKey:@"paperRect"];
    [render setValue:[NSValue valueWithCGRect:printableRect] forKey:@"printableRect"];

    // Call the printToPDF helper method that will do the actual PDF creation using values set above
    NSData *pdfData = [render createPDF];

    // Save the PDF to a file, if creating one is successful
    if (pdfData) {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *path = [paths objectAtIndex:0];

        NSString *pdfPath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"Purchase Order.pdf"]];

        [pdfData writeToFile:pdfPath atomically:YES];
    }
    else
    {
        NSLog(@"error creating PDF");
    }
}

PDFSize is defined as a constant, set to a standard A4 page size. It can be edited to meet your needs.

#define PDFSize CGSizeMake(595.2,841.8)
Vel Genov
  • 10,513
  • 2
  • 16
  • 19
  • I get an error with this code: PDFSize is not defined. what should it be. Also when I press my share button I have, the app crashes `attempt to insert nil object from objects[0]` I have updated my question with my share button – user979331 May 18 '15 at 17:59
  • PDFSize is a constant. It basically defines your page size. I would use something standard here, and that's why I have it set to A4. You can modify it to get the page size you need. – Vel Genov May 18 '15 at 18:03
  • As far as your share() action, you will run into a problem, when the PDF is not available at the path you have defined. There are a couple of things you can try - make sure you use the same path for saving, and for sharing the file. The other thing, and this one is more important, make sure the PDF file is actually created and available in that path. I tested the action on my end, and I'm not getting the crash. – Vel Genov May 18 '15 at 18:11
  • @VelGenov : I am creating PDF the same way for Microsoft documents. But here, PDF that is created have grey background. Can I change it ? – Mansi Panchal Dec 21 '16 at 11:42
  • @MansiPanchal, this thread is specific to viewing a PDF in iOS. You might be seeing a separate issue when creating a PDF in a Microsoft environment. You will have to post a separate question for this and add some details to it. Thanks. – Vel Genov Dec 21 '16 at 14:52
2

In order to create your PDF file in memory, you need to draw the layer of the UIWebBrowserView instance that lies underneath the UIWebView's scrollView. In order to do that, try changing your renderInContext: call the following way :

UIView* contentView = webViewPDF.scrollView.subviews.firstObject;
[contentView.layer renderInContext:currentContext];

Also, if you target iOS >= 7.0, then you can avoid using renderInContext: and use one of the snapshotViewAfterScreenUpdates: or drawViewHierarchyInRect:afterScreenUpdates: methods.

Dalzhim
  • 1,970
  • 1
  • 17
  • 34