91

There are a lot of resources about how to display a PDF in an App's UIView. What I am working on now is to create a PDF from UIViews.

For example, I have a UIView, with subviews like Textviews, UILabels, UIImages, so how can I convert a big UIView as a whole including all its subviews and subsubviews to a PDF?

I have checked Apple's iOS reference. However, it only talks about writing pieces of text/image to a PDF file.

The problem I am facing is that the content I want to write to a file as PDF is a lot. If I write them to the PDF piece by piece, it is going to be huge work to do. That's why I am looking for a way to write UIViews to PDFs or even bitmaps.

I have tried the source code I copied from other Q/A within Stack Overflow. But it only gives me a blank PDF with the UIView bounds size.

-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename
{
    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [NSMutableData data];

    // Points the pdf converter to the mutable data object and to the UIView to be converted
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();

    // draws rect to the view and thus this is captured by UIGraphicsBeginPDFContextToData
    [aView drawRect:aView.bounds];

    // remove PDF rendering context
    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];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Cullen SUN
  • 3,517
  • 3
  • 32
  • 33

7 Answers7

126

Note that the following method creates just a bitmap of the view; it does not create actual typography.

(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename
{
    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [NSMutableData data];

    // Points the pdf converter to the mutable data object and to the UIView to be converted
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();


    // draws rect to the view and thus this is captured by UIGraphicsBeginPDFContextToData

    [aView.layer renderInContext:pdfContext];

    // remove PDF rendering context
    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];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);
}

Also make sure you import: QuartzCore/QuartzCore.h

Fattie
  • 27,874
  • 70
  • 431
  • 719
casillic
  • 1,837
  • 1
  • 24
  • 29
  • 2
    +1 I went through several posts on pdf generation before finding this simple solution. – Jason George Aug 09 '11 at 16:35
  • 7
    I wanted to do the same and your method seems to work fine but its quality is very low. Did I miss anything? – iEamin Oct 30 '11 at 04:55
  • 8
    I suspect the quality is pretty low because it's taking the UIView and converting it to a raster, where as the other methods of rendering text and images directly preserves them as vectors in the PDF file. – joshaidan Jun 21 '12 at 15:41
  • 3
    I am following this method , but I am getting a blank pdf generated . can any one help me ? – Raj Oct 30 '12 at 05:54
  • It works awesome !!! cheers !! the only problem that I have is, it generate PDF in just one page. How can i Separate pages instead of having a long PDF file ?! – Rudi Mar 17 '13 at 08:12
  • Gives me a blank screen on iOS6.1 – Alex Stone Apr 20 '13 at 15:59
  • Hi this works fine but it doesn't create pdf for whole uitableview. Can you please suggest how to take pdf for invisible cells as well? – Paresh Masani Jul 17 '13 at 21:19
  • Seems this works ok. Except if the view scrolls. In which case it only renders visible content. I imagine aview.bounds is controlling this ? – Grant B Aug 06 '13 at 05:18
  • I found that I was getting a blank screen when I ran the code on the view did load method. Maybe try and run the method when a button is pressed or something and it should be fine. The quality is fantastic thanks Casillic! – NashT Sep 26 '13 at 13:09
  • iOS 5.1 get half page, is there normal ? – Daniel Arantes Loverde Oct 03 '13 at 20:09
  • This draws my view such that it fills the page. If I use drawInContext it's the right size but in the top left. How do I tell it where to draw the view? – shim Oct 30 '13 at 16:38
  • hey Casilic thats a great answer!! but i have some doubt like if i have a form which has 40-50 text field and 10-20 text view and user has entered 1000 words in text view then how can i show all text view data – amit soni Feb 05 '14 at 07:05
  • @amit soni -This method is really for onscreen info only. You probably should generate a report screen from the user's info and have that report converted to PDF. Best of luck. – casillic Feb 05 '14 at 13:27
  • hey see this html2pdf cordova plugin https://github.com/moderna/cordova-plugin-html2pdf/blob/master/src/ios/Html2pdf.h it can print scrollable webview into multipage pdf – Qing Dec 14 '14 at 12:57
  • this crashes in ios 8 at `UIGraphicsEndPDFContext` – Jay Mayu Feb 05 '15 at 08:59
  • Can i somehow convert this in PNG or JPG ? because i want to be able to save it in camera roll as well as use in my application. Very nice solution though +1 – Ahsan Ebrahim Khatri Mar 08 '16 at 13:16
  • @casillic your code works fine for UIView but I need for UIScrollview. I am using UIScrollview instead of UIView in parameter and using UIScrollview's ContentSize insted of UIView bound. This thing generate PDF but only for visible portion. You have any proper solution for UIScrollview ? – Abha Jul 27 '16 at 06:31
25

Additionally, if anyone is interested, here is the Swift 3 code:

func createPdfFromView(aView: UIView, saveToDocumentsWithFileName fileName: String)
{
    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil)
    UIGraphicsBeginPDFPage()

    guard let pdfContext = UIGraphicsGetCurrentContext() else { return }

    aView.layer.render(in: pdfContext)
    UIGraphicsEndPDFContext()

    if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
        let documentsFileName = documentDirectories + "/" + fileName
        debugPrint(documentsFileName)
        pdfData.write(toFile: documentsFileName, atomically: true)
    }
}
retrovius
  • 782
  • 1
  • 10
  • 25
11

If someone is interested, here's Swift 2.1 code:

    func createPdfFromView(aView: UIView, saveToDocumentsWithFileName fileName: String)
    {
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil)
        UIGraphicsBeginPDFPage()

        guard let pdfContext = UIGraphicsGetCurrentContext() else { return }

        aView.layer.renderInContext(pdfContext)
        UIGraphicsEndPDFContext()

        if let documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first {
            let documentsFileName = documentDirectories + "/" + fileName
            debugPrint(documentsFileName)
            pdfData.writeToFile(documentsFileName, atomically: true)
        }
    }
Denis Rumiantsev
  • 187
  • 1
  • 2
  • 8
  • Your guard statement means the UIGraphicsEndPDFContext() isn't called - maybe add a defer earlier? – David H Feb 13 '16 at 17:05
  • @DavidH thanks, David, good idea! Also, I think, there's a good idea add a completion block kind `completion: (success: Bool) -> ()` for guard return cases – Denis Rumiantsev Feb 17 '16 at 19:02
  • 1
    Yesterday I posted a Q&A on how to produce a high resolution image by rendering the view in a large image, then drawing the image into a PDF in interested: http://stackoverflow.com/a/35442187/1633251 – David H Feb 17 '16 at 20:43
8

A super easy way to create a PDF from UIView is using UIView Extension

Swift 4.2

extension UIView {

  // Export pdf from Save pdf in drectory and return pdf file path
  func exportAsPdfFromView() -> String {

      let pdfPageFrame = self.bounds
      let pdfData = NSMutableData()
      UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
      UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)
      guard let pdfContext = UIGraphicsGetCurrentContext() else { return "" }
      self.layer.render(in: pdfContext)
      UIGraphicsEndPDFContext()
      return self.saveViewPdf(data: pdfData)

  }

  // Save pdf file in document directory
  func saveViewPdf(data: NSMutableData) -> String {  
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let docDirectoryPath = paths[0]
    let pdfPath = docDirectoryPath.appendingPathComponent("viewPdf.pdf")
    if data.write(to: pdfPath, atomically: true) {
        return pdfPath.path
    } else {
        return ""
    }
  }
}

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

Ashish Chauhan
  • 1,316
  • 15
  • 22
  • Thanks it worked, One question, So I have long scroll view, but the PDF file only shows a portion of it, so is there a way to tweak your code for example to give it Height ? – Hussein Apr 05 '19 at 16:39
  • @HusseinElbeheiry just use contentView to generate pdf. When I create a scrollView (UIScrollView) I will definitely create a contentView (UIView) and put the contentView in the scrollView, and I add all subsequent elements to the contentView. In this case, it is enough to use contentView to create a PDF document. contentView.exportAsPdfFromView – iAleksandr Oct 09 '19 at 00:10
  • Where i can find the saved PDF file? – Midhun Narayan Jun 24 '21 at 10:29
  • @MidhunNarayan inside your app document directory. Just print the pdfPath on console and access it. – Ashish Chauhan Jun 30 '21 at 07:44
  • 1
    @AshishChauhan i have printed the path but when i open my file app it is not showing. do i need anything extra to see the converted pdf in my file app – Midhun Narayan Jul 01 '21 at 13:07
5

With Swift 5 / iOS 12, you can combine CALayer's render(in:) method with UIGraphicsPDFRenderer's writePDF(to:withActions:) method in order to create a PDF file from a UIView instance.


The following Playground sample code shows how to use render(in:) and writePDF(to:withActions:):

import UIKit
import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .orange
let subView = UIView(frame: CGRect(x: 20, y: 20, width: 40, height: 60))
subView.backgroundColor = .magenta
view.addSubview(subView)

let outputFileURL = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("MyPDF.pdf")
let pdfRenderer = UIGraphicsPDFRenderer(bounds: view.bounds)

do {
    try pdfRenderer.writePDF(to: outputFileURL, withActions: { context in
        context.beginPage()
        view.layer.render(in: context.cgContext)
    })
} catch {
    print("Could not create PDF file: \(error)")
}

Note: in order to use playgroundSharedDataDirectory in your Playground, you first need to create a folder called "Shared Playground Data" in you macOS "Documents" folder.


The UIViewController subclass complete implementation below shows a possible way to refactor the previous example for an iOS app:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.backgroundColor = .orange
        let subView = UIView(frame: CGRect(x: 20, y: 20, width: 40, height: 60))
        subView.backgroundColor = .magenta
        view.addSubview(subView)

        createPDF(from: view)
    }

    func createPDF(from view: UIView) {
        let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let outputFileURL = documentDirectory.appendingPathComponent("MyPDF.pdf")
        print("URL:", outputFileURL) // When running on simulator, use the given path to retrieve the PDF file

        let pdfRenderer = UIGraphicsPDFRenderer(bounds: view.bounds)

        do {
            try pdfRenderer.writePDF(to: outputFileURL, withActions: { context in
                context.beginPage()
                view.layer.render(in: context.cgContext)
            })
        } catch {
            print("Could not create PDF file: \(error)")
        }
    }

}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
2

This will generate PDF from UIView and open print dialog, objective C. Attach - (IBAction)PrintPDF:(id)sender to your button on screen. Add #import <QuartzCore/QuartzCore.h> framework

H File

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

M File

-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename

{
    NSMutableData *pdfData = [NSMutableData data];

    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();


    [aView.layer renderInContext:pdfContext];
    UIGraphicsEndPDFContext();

    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];
    NSString *file = [documentDirectory stringByAppendingPathComponent:@"yourPDF.pdf"];
    NSURL *urlPdf = [NSURL fileURLWithPath: file];

    [pdfData writeToFile:documentDirectoryFilename atomically:YES];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);

}


- (IBAction)PrintPDF:(id)sender
{
    [self createPDFfromUIView:self.view saveToDocumentsWithFileName:@"yourPDF.pdf"];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"yourPDF.pdf"];
    NSData *myData = [NSData dataWithContentsOfFile: path];

    UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
    if(pic && [UIPrintInteractionController canPrintData: myData] ) {

        pic.delegate = self;

        UIPrintInfo *printInfo = [UIPrintInfo printInfo];
        printInfo.outputType = UIPrintInfoOutputGeneral;
        printInfo.jobName = [path lastPathComponent];
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        pic.printInfo = printInfo;
        pic.showsPageRange = YES;
        pic.printingItem = myData;

        void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) = ^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
            //self.content = nil;
            if(!completed && error){

                NSLog(@"Print Error: %@", error);
            }
        };

        [pic presentAnimated:YES completionHandler:completionHandler];

    }

}
-4

I don't know why, but casilic's answer gives me blank screen on iOS6.1. The code below works.

-(NSMutableData *)createPDFDatafromUIView:(UIView*)aView 
{
    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [NSMutableData data];

    // Points the pdf converter to the mutable data object and to the UIView to be converted
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();


    // draws rect to the view and thus this is captured by UIGraphicsBeginPDFContextToData

    [aView.layer renderInContext:pdfContext];

    // remove PDF rendering context
    UIGraphicsEndPDFContext();

    return pdfData;
}


-(NSString*)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename
{
    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [self createPDFDatafromUIView:aView];

    // 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];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);
    return documentDirectoryFilename;
}
Alex Stone
  • 46,408
  • 55
  • 231
  • 407
  • 17
    This is the same exact code as my answer just broken out in two separate methods???? How do you think this fixed your blank screen problem when it is the same code?? – casillic Apr 20 '13 at 16:43
  • I had the same experience. Got a blank PDF from the first code. Splitting it in two as Alex did, made it work. Can't explain why. – Tom Tallak Solbu Feb 07 '15 at 11:57