Creating PDF file from UIWebView

19,228

Solution 1

Use UIPrintPageRenderer from UIWebView Follow below steps :

Add Category of UIPrintPageRenderer for getting PDF Data

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

@implementation UIPrintPageRenderer (PDF)
- (NSData*) printToPDF
{
  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

Add these define for A4 size

#define kPaperSizeA4 CGSizeMake(595.2,841.8)

Now in UIWebView's webViewDidFinishLoad delegate use UIPrintPageRenderer property of UIWebView.

- (void)webViewDidFinishLoad:(UIWebView *)awebView
{
  if (awebView.isLoading)
    return;

  UIPrintPageRenderer *render = [[UIPrintPageRenderer alloc] init];
  [render addPrintFormatter:awebView.viewPrintFormatter startingAtPageAtIndex:0];
  //increase these values according to your requirement
  float topPadding = 10.0f;
  float bottomPadding = 10.0f;
  float leftPadding = 10.0f;
  float rightPadding = 10.0f;
  CGRect printableRect = CGRectMake(leftPadding,
                                  topPadding,
                                  kPaperSizeA4.width-leftPadding-rightPadding,
                                  kPaperSizeA4.height-topPadding-bottomPadding);
  CGRect paperRect = CGRectMake(0, 0, kPaperSizeA4.width, kPaperSizeA4.height);
  [render setValue:[NSValue valueWithCGRect:paperRect] forKey:@"paperRect"];
  [render setValue:[NSValue valueWithCGRect:printableRect] forKey:@"printableRect"];
  NSData *pdfData = [render printToPDF];
  if (pdfData) {
    [pdfData writeToFile:[NSString stringWithFormat:@"%@/tmp.pdf",NSTemporaryDirectory()] atomically: YES];
  }
  else
  {
    NSLog(@"PDF couldnot be created");
  }
}

Solution 2

Thank you for your answers Firemarble, it helped me a lot. I managed to get a somewhat good looking results with this method :

-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename
{
    // Creates a mutable data object for updating with binary data, like a byte array
    UIWebView *webView = (UIWebView*)aView;
    NSString *heightStr = [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"];
    
    int height = [heightStr intValue];
    //  CGRect screenRect = [[UIScreen mainScreen] bounds];
    //  CGFloat screenHeight = (self.contentWebView.hidden)?screenRect.size.width:screenRect.size.height;
    CGFloat screenHeight = webView.bounds.size.height;
    int pages = ceil(height / screenHeight);
    
    NSMutableData *pdfData = [NSMutableData data];
    UIGraphicsBeginPDFContextToData(pdfData, webView.bounds, nil);
    CGRect frame = [webView frame];
    for (int i = 0; i < pages; i++) {
        // Check to screenHeight if page draws more than the height of the UIWebView
        if ((i+1) * screenHeight  > height) {
            CGRect f = [webView frame];
            f.size.height -= (((i+1) * screenHeight) - height);
            [webView setFrame: f];
        }
        
        UIGraphicsBeginPDFPage();
        CGContextRef currentContext = UIGraphicsGetCurrentContext();
        
        [[[webView subviews] lastObject] setContentOffset:CGPointMake(0, screenHeight * i) animated:NO];
        [webView.layer renderInContext:currentContext];
    }
    
    UIGraphicsEndPDFContext();
    // Retrieves the document directories from the iOS device
    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
    
    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];
    
    // instructs the mutable data object to write its context to a file on disk
    [pdfData writeToFile:documentDirectoryFilename atomically:YES];
    [webView setFrame:frame];
}

Solution 3

Swift 2.0 solution, how to save pdf from webview to NSData:

func webViewDidFinishLoad(webView: UIWebView) {
    let pdfData = createPdfFile(webView.viewPrintFormatter())
    pdfData.writeToFile("/path/to/file", atomically: true)
}

func createPdfFile(printFormatter: UIViewPrintFormatter) -> NSData {
    let renderer = UIPrintPageRenderer()
    renderer.addPrintFormatter(printFormatter, startingAtPageAtIndex: 0);
    let paperSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height)
    let printableRect = CGRectMake(0, 0, paperSize.width, paperSize.height)
    let paperRect = CGRectMake(0, 0, paperSize.width, paperSize.height);
    renderer.setValue(NSValue(CGRect: paperRect), forKey: "paperRect")
    renderer.setValue(NSValue(CGRect: printableRect), forKey: "printableRect")
    return renderer.printToPDF()
}

You also need to add UIWebViewDelegate to your ViewController and extension for UIPrintPageRenderer.

extension UIPrintPageRenderer {
    func printToPDF() -> NSData {
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, self.paperRect, nil)
        self.prepareForDrawingPages(NSMakeRange(0, self.numberOfPages()))
        let bounds = UIGraphicsGetPDFContextBounds()
        for i in 0..<self.numberOfPages() {
            UIGraphicsBeginPDFPage();
            self.drawPageAtIndex(i, inRect: bounds)
        }
        UIGraphicsEndPDFContext();
        return pdfData;
    }
}

Solution 4

Swift 3 solution of @Dan Loewenherz answer:

func webViewDidFinishLoad(_ webView: UIWebView) {
    let pdfData = createPdfFile(printFormatter: webView.viewPrintFormatter())
    pdfData.write(toFile: "/path/to/file", atomically: true)
}

func createPdfFile(printFormatter: UIViewPrintFormatter) -> NSData {
    let renderer = UIPrintPageRenderer()
    renderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
    let point = CGPoint(x:0,y:0)
    let paperSize = CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
    let printableRect = CGRect(origin: point, size: CGSize(width:paperSize.width, height: paperSize.height))
    let paperRect = CGRect(origin: point, size: CGSize(width: paperSize.width, height: paperSize.height))
    renderer.setValue(NSValue(cgRect: paperRect), forKey: "paperRect")
    renderer.setValue(NSValue(cgRect: printableRect), forKey: "printableRect")
    return renderer.printToPDF()
}

extension UIPrintPageRenderer {
    func printToPDF() -> NSData {
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, self.paperRect, nil)
        self.prepare(forDrawingPages: NSMakeRange(0, self.numberOfPages))
        let bounds = UIGraphicsGetPDFContextBounds()
        for i in 0..<self.numberOfPages {
            UIGraphicsBeginPDFPage();
            self.drawPage(at: i, in: bounds)
        }
        UIGraphicsEndPDFContext();
        return pdfData;
    }
}

Solution 5

UIWebView is deprecated now. Use WKWebView extension to create PDF from WKWebView

Credit: http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/

Swift 4.2

extension WKWebView {

    // Call this function when WKWebView finish loading
    func exportAsPdfFromWebView() -> String {
        let pdfData = createPdfFile(printFormatter: self.viewPrintFormatter())
        return self.saveWebViewPdf(data: pdfData)
    }

    func createPdfFile(printFormatter: UIViewPrintFormatter) -> NSMutableData {
        let originalBounds = self.bounds
        self.bounds = CGRect(x: originalBounds.origin.x,
                             y: bounds.origin.y,
                             width: self.bounds.size.width,
                             height: self.scrollView.contentSize.height)
        let pdfPageFrame = CGRect(x: 0, y: 0, width: self.bounds.size.width,
                                  height: self.scrollView.contentSize.height)
        let printPageRenderer = UIPrintPageRenderer()
        printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
        printPageRenderer.setValue(NSValue(cgRect: UIScreen.main.bounds), forKey: "paperRect")
        printPageRenderer.setValue(NSValue(cgRect: pdfPageFrame), forKey: "printableRect")
        self.bounds = originalBounds
        return printPageRenderer.generatePdfData()
    }

    // Save pdf file in document directory
    func saveWebViewPdf(data: NSMutableData) -> String {

        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let docDirectoryPath = paths[0]
        let pdfPath = docDirectoryPath.appendingPathComponent("webViewPdf.pdf")
        if data.write(to: pdfPath, atomically: true) {
            return pdfPath.path
        } else {
            return ""
        }
    }
}
extension UIPrintPageRenderer {

    func generatePdfData() -> NSMutableData {
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, self.paperRect, nil)
        self.prepare(forDrawingPages: NSMakeRange(0, self.numberOfPages))
        let printRect = UIGraphicsGetPDFContextBounds()
        for pdfPage in 0..<self.numberOfPages {
            UIGraphicsBeginPDFPage()
            self.drawPage(at: pdfPage, in: printRect)
        }
        UIGraphicsEndPDFContext();
        return pdfData
    }
}

It will save PDF file in the directory and returns pdf file path.

Share:
19,228
AnderCover
Author by

AnderCover

Developer since 2011 iOS Developer since 2011 Switched to swift but still &lt;3 Objective-C

Updated on June 20, 2022

Comments

  • AnderCover
    AnderCover almost 2 years
     -(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename
    {
        // Creates a mutable data object for updating with binary data, like a byte array
      UIWebView *webView = (UIWebView*)aView;
        NSString *heightStr = [webView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"];
    
        int height = [heightStr intValue];
    
        // Get the number of pages needed to print. 9 * 72 = 648
        int pages = ceil(height / 648.0);
    
        NSMutableData *pdfData = [NSMutableData data];
        UIGraphicsBeginPDFContextToData( pdfData, CGRectZero, nil );
      CGRect frame = [webView frame];
        for (int i = 0; i < pages; i++) {
          // Check to see if page draws more than the height of the UIWebView
            if ((i+1) * 648 > height) {
                CGRect f = [webView frame];
                f.size.height -= (((i+1) * 648.0) - height);
                [webView setFrame: f];
            }
    
            UIGraphicsBeginPDFPage();
            CGContextRef currentContext = UIGraphicsGetCurrentContext();
            CGContextTranslateCTM(currentContext, 72, 72); // Translate for 1" margins
    
            [[[webView subviews] lastObject] setContentOffset:CGPointMake(0, 648 * i) animated:NO];
            [webView.layer renderInContext:currentContext];
        }
    
        UIGraphicsEndPDFContext();
        // Retrieves the document directories from the iOS device
      NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
    
      NSString* documentDirectory = [documentDirectories objectAtIndex:0];
      NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];
    
        // instructs the mutable data object to write its context to a file on disk
      [pdfData writeToFile:documentDirectoryFilename atomically:YES];
      [webView setFrame:frame];
      NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);
    }
    

    I'm using the code above to generate a pdf file from a webview. It's working, however, the content is not cropped correctly : the content on the bottom of the pages get messed up. I think I could do something better using Core Graphics methods but I can't find how to do that. Any ideas ?

  • AnderCover
    AnderCover about 11 years
    It's worse, I'm on iPad though so I think that the hardcoded 648 value is wrong. Un fortunately when I use the screen height my pdf document doesn't even start at the beginning of my webview
  • user-44651
    user-44651 about 11 years
    What was your reasoning for using 648 then?
  • user-44651
    user-44651 about 11 years
    NSMutableData *pdfData = [NSMutableData data]; UIGraphicsBeginPDFContextToData(pdfData, webView.bounds, nil); UIGraphicsBeginPDFPage(); CGContextRef pdfContext = UIGraphicsGetCurrentContext(); UIGraphicsBeginPDFContextToData [webView.layer renderInContext:pdfContext]; UIGraphicsEndPDFContext();
  • AnderCover
    AnderCover about 11 years
    Well there was no reasoning whatsoever... Don't get met wrong I agree that 648 doesn't fit iPad size, I just didn't pay attention to it.
  • AnderCover
    AnderCover about 11 years
    Don't understand your code sample ? what should I do with it ?
  • user-44651
    user-44651 about 11 years
    Look at this line you have UIGraphicsBeginPDFContextToData( pdfData, CGRectZero, nil ); Try removing CGRectZero and replacing it with webView.bounds
  • AnderCover
    AnderCover about 11 years
    Thank you for your answers Firemarble, it helped me a lot. I managed to get a somewhat good looking results with this method :
  • Tib
    Tib over 9 years
    Great job! You saved my day :)
  • AnderCover
    AnderCover over 9 years
    Hello, it's an old question, but i'll try your solution. It seems better than what I did back then.
  • Michael Kernahan
    Michael Kernahan over 9 years
    I wish I had a million upvotes to give you. Fantastic.
  • santhosh
    santhosh about 9 years
    Here i can render the pdf from uiwebview. But i can't render the pdf with the subviews in uiwebview. i am facing this problem using the above code.
  • MobileMon
    MobileMon about 8 years
    The orientation is landscape, how to convert to portrait?
  • Shin622
    Shin622 almost 7 years
    excuse me, I have used your function to create a pdf from webview and it working perfectly, I added a UILabel to webView.scrollView for display some text on PDF. Do you know how can I create PDF with the extra text ?