Tutorial

How to Generate PDF using HTML Templates and UIPrintPageRenderer in iOS


Have you ever been asked to create PDF documents with content straight from your app? Have you ever even thought how to do that, if you’ve never done it before?

Well, beginning the post by setting questions is a bit of unorthodox way to start, but the above summarise what I’m about to talk in this text. The idea of creating a PDF document in an iOS app looks quite often like the road through hell, but it doesn’t have to be like that. As a developer, you have always to be resourceful and create yourself alternatives and options to choose from and avoid reaching your goal at any cost. Nobody wants that. I have to admit that drawing a PDF page manually can become eventually a really painful process (depending on the content), and doing so can turn into an unproductive task at the end. Calculating points, adding lines, setting colors, insets, offsets, etc, might be interesting (or not), but definitely things can go really messy if you have complicated documents to draw.

My goal here is to present you a different way to create PDF documents, which is way more simpler than drawing them manually. The whole idea is based on using HTML templates and it can be synopsised in the following steps:

  1. Creation of HTML templates for the forms or the content needed to be printed to PDF.
  2. Use of those HTML templates to produce the real content (optionally display it in a web view).
  3. Printing the real HTML content to PDF.

In the last step, iOS will do all the hard work for you.

Let me be more thorough now. I think that you totally agree to the fact that it’s always preferable to deal with HTML instead of drawing PDFs directly. All you actually want in that case is to manage to represent your content as an HTML page, but it’s not wise or productive to manually create pages for repeated content. For example, think of an app that prints or exports personal information for school students to PDF. Creating an HTML page manually for every single student is out of the question, as the same pattern is followed for printing the info for all students. What you really want is to create one HTML page only, which is going to be the template. Instead of using real values, you set placeholders in the key points that you mark in a unique way, and then inside your app you replace those placeholders with real values. You understand of course that the value replacement in the last step is an automated process and can be done repeatedly.

By the time you have some real content formed as HTML code, you can do whatever it’s possible to do with it. That means that you can display it in a web view, to save it to external files, to share it, and of course, to print it as PDF.

So, what exactly are we going to go through the next parts?

The ultimate goal is to show to you how to print content to a PDF document. But we’ll get there based on HTML templates with placeholder values that will be replaced by real values. The demonstration app we’ll use is a simple invoice maker, which I think that perfectly fits to the needs of printing data to PDF. We are not going to build that app from scratch, it’s not our goal after all. The default functionality will be given to you already made. You’ll also be given the HTML templates, and we’ll go through them so you have the chance to understand what is all about, and what the meaning of placeholders is. But no matter what, we’ll go together and step by step through the process of composing the real HTML content, and then printing it to a PDF document. And we won’t stop there; I’ll also show you how to add header and footer content to your final PDF.

Interested in all the above? Let’s get started then!

The Starter Project

We’ll begin by taking a quick tour to the demo app of this tutorial, which actually is an invoice maker tool. Before I start talking about it, you have to download a starter project first that we’ll work on. Once you do so, open it in Xcode.

In that starter project you’ll find quite enough job already been done. The “landing” view controller named InvoiceListViewController is used to display a list of invoices created and saved inside the app. This is also the place where you can initiate the creation of a new invoice by using the plus bar button to the top-right side of the screen. By tapping on any row matching to an invoice you’ll be guided to a preview screen, where you can see and print the invoice as a PDF document. Note that this part of functionality doesn’t exist in the starter project; it’s what we are going to do in this tutorial. Finally, a created invoice can be deleted by swiping to the left on a cell. The following screenshot demonstrates that view controller:

t54_1_invoice_list_viewcontroller

As I said, a new invoice can be created by tapping on the plus button. That action takes us to a new view controller called CreatorViewController which looks like that:

t54_2_creator_viewcontroller

An invoice has generally a variety of values needed to be filled in before it can be printed. Some of those values can be set manually in that view controller, some others are calculated automatically, and some others will be hardcoded later in code. To make that specific, the values that can be manually added in our demo app are:

  • The recipient info, which actually is the person the invoice is addressed to. This is the grey area in the above figure.
  • The invoice items, where each item has two parts: The description of the service provided, and the price for that service. We’ll keep things simple and we’ll have no VAT here. An item can be added here by using the plus button in the toolbar to the bottom of the screen (more about it in a while).

The automatically calculated values are:

  • The invoice number (the number shown as a title to the navigation bar).
  • The total amount of the invoice (shown to the left side of the bottom toolbar).

Lastly, the invoice values that we’ll hardcode later are:

  • The sender information, which is the issuer’s information.
  • The due date of the invoice (that we won’t use but set it to blank in case you want to use it).
  • The payment method.
  • The logo of the invoice.

Regarding the invoice items, a new view controller named AddItemViewController exists in the project for allowing easy data entry. There are two textfields only for filling the respective values in, and a save button to add them to the invoice and return to the previous view controller:

t54_3_add_item_viewcontroller

All the invoice items are appended to an array of dictionaries, where each dictionary has two values: The description and the price of each item. That array is used as the datasource for the tableview that lists all items in the CreatorViewController. When an invoice gets saved, then both the manually added data and the automatically calculated data is set to a dictionary and being returned to the InvoiceListViewController. Here’s what is returned:

  • The invoice number (string value)
  • The recipient info (string value)
  • The total amount (string value)
  • The invoice items (array with dictionaries).

Upon saving an invoice the next invoice number is calculated and stored for future use in the user defaults dictionary (NSUserDefaults). The dictionary with the data of a new invoice is appended to an array in the InvoiceListViewController, and that array is stored to the user defaults as well every time a new one is created by the user. The invoice data is loaded from the user defaults when that view controller is about to appear. Keep in mind that saving to user defaults dictionary the main data of the app is a quick solution for the sake of the demo app, and it’s not recommended as an approach in a real application. There are definitely better ways to store your data.

I have almost nothing to comment regarding the existing code, and all you have to do is to go through each view controller or follow the flow of the app so as you see the details of the implementation. There’s just one point I’d like to mention about, and that is the AppDelegate.swift file. You’ll find three convenient methods there: One for getting easy access to the application delegate, one for getting the path to the documents directory, and one for converting an amount represented as a string into a currency string (the amount along with the proper currency symbol). Even though these methods are already being used in the starter project, we’re going to make use of them again later as well. In the AppDelegate you’ll also find a property named currencyCode set to “eur” (Euro currency) by default. Use it to set your own currency code.

Lastly, let me tell you where the starter project ends, and where exactly we’re about to start here. By tapping on an existing invoice in the tableview in the InvoiceListViewController, a dictionary containing the data of the matching invoice is passed to the PreviewViewController view controller. In this one, there’s a web view to preview the invoice rendered as an HTML document, and a button to export to PDF. These functionalities do not exist in the starter project and we will start implementing them, taking as granted that all the data we need regarding an invoice already exists in the PreviewViewController and we can use it directly.

The HTML Template Files

As I explained in the introduction, we’re going to use HTML templates to initially produce an invoice, and then the real HTML content will be printed to a PDF file. The primary logic here is to put placeholders in the HTML files in certain points, and then replace those placeholders with real data. But in order to do that, we must find or create custom HTML forms that will give us eventually the desired result. For the purposes of this tutorial we won’t create any custom HTML invoice templates. Instead, we’ll use the one found here (with special thanks to the creators). That template has been modified a little bit, so there’s no more shadow and a border around it, while a grey background color has been applied to the logo.

In the starter project that you’ve downloaded, you’ll find three HTML files:

  1. invoice.html
  2. last_item.html
  3. single_item.html

The first one contains the code that will produce the entire invoice, except for the lines of the items. We have two other templates for the items specifically: single_item.html will be used to display an item in any row except for the last one, and last_item.html will be used to represent the last item only. That’s because the bottom border line is different for the last row.

A placeholder inside any HTML template file will be a special keyword surrounded by # symbols. For example, the next extract show the placeholders for the invoice number, issue date and due date:

Note: Even though the due date exists as a placeholder, we won’t really use it. We’ll replace it with an empty string, but it’ll be there for you to use it if you want.

You can find all placeholders inside the three HTML files, and the position where each placeholder fits to. Here’s a list with all of them:

  • #LOGO_IMAGE#
  • #INVOICE_NUMBER#
  • #INVOICE_DATE#
  • #DUE_DATE#
  • #SENDER_INFO#
  • #RECIPIENT_INFO#
  • #PAYMENT_METHOD#
  • #ITEMS#
  • #TOTAL_AMOUNT#
  • #ITEM_DESC#
  • #PRICE#

The last two placeholders exist in the single_item.html and last_item.html files only. Also, the #ITEMS# placeholder will be replaced once the respective code for all items has been created by using the two extra HTML template files (details about that in the proper time).

As you can see, preparing one or more HTML templates in order to create custom output for a form (in that case it’s the invoice) it’s not difficult. And after having gone through the whole process here, you’ll realise that generating real content based on those templates and then exporting it to PDF files is easy and effective.

Composing the Content

Having gone through the demo app and the invoice templates, it’s time for us now to get started and complete the app by implementing all the key missing parts. What we want to do initially, is to use the HTML templates and create the actual HTML content for an invoice that gets selected in the first view controller (InvoiceListViewController). After having done that, we’ll display the generated HTML code in the web view existing in the PreviewViewController view controller, and we’ll be able to verify that way that the job has been properly done.

The main and most important task in this part is the replacement of the placeholder values in the invoice HTML template files with real values. Those real values are actually the data matching to the selected invoice in the InvoiceListViewController that pass to the PreviewViewController. As you’ll see next, the replacement of the placeholders is a straightforward task. Before we get to that though, let’s create a new class that we’ll use for generating the real the HTML content and later to print to PDF. So, in Xcode go to File > New > File… menu and create a new Cocoa Touch Class. Make it a subclass of the NSObject class, and name it InvoiceComposer. Proceed with the guide and get finished with the new file creation.

t54_4_create_invoice_composer

With the InvoiceComposer.swift file now existing in Xcode, select it in the Project Navigator to open it. We’ll begin by declaring some properties (both constants and variables):

With the first three properties (pathToInvoiceHTMLTemplate, pathToSingleItemHTMLTemplate, pathToLastItemHTMLTemplate) we specify the paths to the HTML template files. These paths will become handy in a while as we’ll need to open them all and get the template code in order to modify it.

As I have already said, our demo app doesn’t provide options to set all invoice parameters (senderInfo, dueDate, paymentMethod, logoImageURL), so the specific ones are hardcoded here. In a real app of course, these would consist of values that users should be able to set or change. The last one is the URL of the image that I’ve chosen as the logo for the invoices. Needless to say that you are totally free to set your own values in the above properties (for example, set your own info in the senderInfo property).

Lastly, the invoiceNumber property will hold the number of the invoice that’s being previewed at any given moment, and the pdfFilename will contain the path to the PDF file. That’s something we’ll need later, not now; however it’s the best time to declare it so we’re ready to use it when the time comes.

Except for the above properties, add the default init() method to the class:

We can now proceed and create a new method that will do the important work of replacing the placeholder values in the HTML template files. We’ll name it renderInvoice, and here are the parameters that will get:

The parameters are actually all the values that can be manually set when creating an invoice in the demo app, and they are all we need in order to generate the invoice (along with the hardcoded values as properties of this class). What we’ll get back from it is a String value that will contain the final HTML with the real content.

Let’s start implementing that method and let’s perform our first important tasks. In the following snippet, two significant things are taking place: At first the template content from the invoice.html file is loaded to a string variable so we can modify it, and then we replace all the placeholders at once except for the invoice items. There are comments that will help you go through it:

Replacing a placeholder value is as simple as it looks in the code above. By using the stringByReplacingOccurrencesOfString(...) string method, we provide the placeholder as the first argument (string to be replaced) and the real value as the second (string to replace), and that’s all for it. It might be a little “boring” to do the same thing for a big number of placeholders, but it’s not difficult in any case at all.

Further than that, notice that all the work is taking place inside a do-catch statement, as the initialisation of the HTMLContent string with the contents of a file can throw an exception. Also, notice that if something goes wrong we return nil, and for now there’s no real value returned with the actual HTML content; it’s coming next.

Let’s focus now on setting the invoice items. As their number may vary, we’ll use a loop to handle them. For each item other than the last one, we’ll be opening the single_item.html template file and we’ll be replacing its placeholders. For the last item only though, we’ll use the last_item.html template as the bottom border line is different. The resulting HTML code will be appended to another string value (the allItems variable you’ll see next), and once that string has been composed with the details of all items, it’ll be used to replace the #ITEMS# placeholder in the HTMLContent string. At the end that string is returned from the method.

Append the following code snippet inside the do body:

Note: I remind you that you can find the implementation of the MARKDOWN_HASH9be45a8449d308e6aba6ff17060c2c17MARKDOWN_HASH and MARKDOWN_HASH43ec7db1bdfab19b8c7d9b31b17d0ccdMARKDOWN_HASH methods in the AppDelegate.swift file.

That’s all for now. The template code has been modified appropriately in order to produce an invoice with real content. Next, we’ll make use of the results of the above method.

Previewing the HTML Content

After having created the real content for the invoice, it’s time to verify that the work has been properly done. Therefore, our goal in this part is to load the composed HTML string to the web view existing in the PreviewViewController, and watch the results of our previous efforts. Note that this is an optional step and in real apps it isn’t necessary to use a web view for previewing the HTML before printing to PDF. We mostly do it here for the sake of the completeness of the demo app.

Switch now to the PreviewViewController.swift file, and go to the top of the class. For starters we’ll declare a couple of new properties:

The first one is an object of the class we created in the previous part and that we’ll initialise shortly. The HTMLContent string variable will be used to hold the real HTML content for future use.

Next, we’ll create a new method where we’ll do the following:

  1. We’ll initialise the invoiceComposer object.
  2. We’ll call the renderInvoice(...) method to generate the invoice HTML code.
  3. We’ll load that HTML to the web view.
  4. We’ll assign the returned HTML string to the HTMLContent property.

Let’s see that:

There’s nothing particularly difficult above, just pay attention to the arguments passing to the renderInvoice(...) method. Once we get an actual HTML string back from that method (not a nil value), we load it to the web view.

Time to call our new method as shown next:

If you want to see the results, run the app and create a new invoice (if you haven’t done it yet). Then select it from the list by tapping on it, and watch the invoice content appearing on the web view, like the one shown below:

t54_5_invoice_webview

Preparing to Print

With the half of the job already done, we can start working in the printing process which will become our way to export an invoice to PDF. There is a special class that we are going to use for our purposes, named UIPrintPageRenderer. If you’ve never used it or heard of it, then let me tell you in short that this class is the one capable of rendering content for printing (either to a file or to a printer using AirPrint). Here‘s the official documentation page, please take a look for more information about that class.

The UIPrintPageRenderer class provides various drawing methods that we don’t really need to override in simple cases. Those drawing methods can be overridden by subclassing the UIPrintPageRenderer class only, but it worths the extra effort as we gain great flexibility and control over the content that will be printed to the actual page, the header or the footer. And in this demo app we’ll need to subclass it because we are going to draw custom header and footer (in a while).

So, back to Xcode again, create a new class following same steps as before. Two things to take care about:

  1. Make it a subclass of the UIPrintPageRenderer class.
  2. Name it CustomPrintPageRenderer.

Once you have it ready (at that point you should be able to see a new file in the Project Navigator called CustomPrintPageRenderer.swift), go there for doing some quick preparation for the next steps. Initially, let’s specify the width and height (in pixels) of the A4 page. Remember that we want the invoice to be exported to PDF, but that PDF could be printed to a real printer as well, so it’s important to limit the paper frame.

The above values describe the precise width and height of the normal, common A4 page that is used all over the world for printing.

It’s necessary to specify now the paper frame and the printing area that an object of our CustomPrintPageRenderer class will draw inside. We’ll do that in the init() method, where obviously the above two properties are meant to be used:

The above consists of a quite straightforward, and at the same time a standard technique for setting the frame of the paper and the area where content will be printed. Both the paperRect and printableRect properties are read-only, and that’s why we set their values that way.

In that snippet, you can see that we made the paper frame and the printable area equal. However, you’ll be wanting to set some insets to the printable area (offset from the page edges), so you end up with better printing results. In that case, you could just replace the last line with the next one:

The above adds an offset of 10 points to both horizontal and vertical axis. Note that the configuration made in this part should be done even if you don’t subclass the UIPrintPageRenderer. In other words, you should never forget to set the paper and printable area of your printing object.

Printing To PDF

By saying “printing to PDF” we actually mean drawing some content to a PDF graphics context. Once that happens, the drawn content can be sent either to a real printer or stored to a file. As we are interested in the second case only, we’ll draw the HTML content into a PDF context, we’ll get the results of the drawing back as a NSData object, and then we’ll save that object to a file (the final .pdf file). Various, but simple steps are part of the process, so let’s see everything one by one.

We’ll proceed here by opening the InvoiceComposer.swift file, where we’ll implement a new method named exportHTMLContentToPDF(...). It will accept one parameter only; the HTML content we want to export to PDF. Before we see the implementation however, it’s necessary to present another printing-related concept. That is the print formatter (UIPrintFormatter class). The following extract is taken from Apple’s documentation:

UIPrintFormatter is an abstract base class for print formatters: objects that lay out custom printable content that can cross page boundaries. Given a print formatter, the printing system can automate the printing of the type of content associated with the print formatter.

That means for us that simply by adding the HTML content as a print formatter to the print page renderer, the iOS printing system will take over of the layout and actual printing to the page. I would advice you to take a look at this page for more information, as everything is explained there. But to keep it simple, think of the print formatter as the mean that we’ll use to pass the content we want to print to the printing system of iOS. In addition to that, it’s necessary to say that even though the UIPrintFormatter is an abstract class, iOS SDK provides concrete subclasses of it to make use of. One of them is the UIMarkupTextPrintFormatter, and that’s what we need for adding the HTML content (markup content) to the print page renderer object. More details about it and for the rest subclasses can be found in the link above.

By having said that, it’s about time to implement our new method. Here it is:

Here’s what happens in that code snippet:

  • Initially we initialise a CustomPrintPageRenderer object that we’ll use to perform the actual drawing (what we call printing).
  • Next we instantiate a UIMarkupTextPrintFormatter object, where we pass the HTML content as a parameter to the initialisation.
  • The page formatter is added to the print page renderer object. The second parameter in the addPrintFormatter(...) method is used to specify the starting page that the print formatter will apply. In our case we set the zero value, as we’re going to have one page only.
  • The actual drawing to PDF is taking place next. The drawPDFUsingPrintPageRenderer(...) is a custom method that we’ll define right after. The results of the drawing to PDF are stored to the pdfData object, which is actually a NSData object.
  • Saving the PDF data to a file is the next step. At first we specify the path to the file, giving at the same time the proper file name (it’s the invoiceNumber property). Then, we just write the PDF data to the that file.
  • The last step is obviously optional, but it’s useful here so we can find the newly created PDF file in Finder. When we’ll run the app, the path to the file will be shown to the console, so we’ll use it to open the PDF in Preview and verify the results.

In a more complicated app you can use multiple print formatter objects, and specify of course different starting pages for each formatter. But for now, the above is just fine for making our point clear.

Now, let’s pass to the implementation of the other custom method that will perform the actual drawing to a PDF context. As you’ll see next, Core Graphics techniques are used to achieve that, but the whole process is short and even self-explanatory. Let’s see it:

At first we initialise a mutable data object, so the PDF output data can be written to it. That’s something we actually dictate the code to do upon the creation of the PDF graphics context (second line). Next, we begin the creation of a new PDF page, but the actual drawing is taking place with that line:

With that line, the print page renderer object that comes as an argument to the method will draw its contents inside the frame of the PDF context. Note that any custom header or footer will be automatically drawn as well, as the drawPageAtIndex(...) calls all the other drawing methods of the print page renderer object.

Lastly, we close the PDF graphics context, while the drawing result data is returned back to the exportHTMLContentToPDF(...) method that calls this one.

The above method prints a single page. However, in case you need to print multiple pages, then you can include the beginning of the PDF page creation and the print page renderer drawing into a loop. Keep that in mind in case you want to extend this demo app or make one of yours and you desire multiple pages to be printed in a single PDF file.

At that point all the tasks related to PDF export have come to their end. We’re not over yet, as in the next part we are going to see how to add custom header and footer to the printed page. However, and before doing that, let’s put all the above in action.

Open the PreviewViewController.swift file, and locate the exportToPDF(...) IBAction method. Add the following line, so the previewed invoice to be exported to a PDF file when tapping on the PDF bar button item:

You can now test the app, but for quick results I would suggest to do so in the Simulator. Once you select an invoice to preview, tap on the PDF bar button item to the top right side of the screen:

t54_6_pdf_button

By doing so, the printing to PDF will take place, and when everything has finished, you’ll see the path to the output file to the console. Copy just the path (without the file name), and open a new Finder window. Use the Shift-Command-G key combination, and paste the path there. In the landing folder you’ll see the newly created PDF file using the invoice number as its name.

t54_7_pdf_in_finder

Double click on it to preview it in the… Preview app (or choose any other app you might like):

PDF preview in ios apps

Drawing Custom Header and Footer

Let’s go now to extend a little bit more this example by adding custom content to the header and footer of the printed page. After all, that’s the reason we subclassed the UIPrintPageRenderer class in first place. By saying custom content, I mean content that is not part of the HTML templates and is not rendered along with the rest of the HTML content. What we want to achieve is to add the word “Invoice” to the top-right side of the page (as a header), and the phrase “Thank you!” to the bottom side of the page (footer) with a horizontal line above it. The following screenshot will make our goal clear:

t54_9_pdf_with_header_footer

Before we see the details of the header and the footer, we must specify the desired height for the header and the footer. Open the CustomPrintPageRenderer.swift file, and add the following two lines in the init() method (note that both properties are from the UIPrintPageRenderer class):

We’ll start working on the header of the page first. We are going to override the following method as it’s originally defined in the UIPrintPageRenderer class:

The steps that we’ll take in the body of that method are outlined right below:

  1. We’ll specify the text we want to draw to the header (the “Invoice” word).
  2. We’ll specify some properties regarding the text formatting, such as the font, the color and the distance between letters.
  3. We’ll calculate the size of the rectangle containing the text after having applied our formatting, and we’ll specify the offset from the right edge of the page.
  4. We’ll determine the starting drawing point of the text.
  5. We’ll draw the text (eventually).

Here’s what I just described converted to code. There are comments that will make it easier to understand each line:

There’s just one thing that I haven’t mentioned in the above code, and that is the use of the getTextSize(...) method. As you correctly guess, it’s another custom method that calculates and returns the size of the text’s frame. That calculation is taking place in a separate method because we’re going to need it when we’ll draw the footer as well.

Here’s the getTextSize(...) method:

The above is a common tactic for calculating the size of the frame surrounding some text. By using a temporary label, we set either a simple text with font or an attributed text, and we use the sizeToFit() method to let the system calculate the exact size for us.

Let’s go now to the footer of the page. The steps followed here are pretty much the same as above, so there’s no need for me to comment almost anything. Just note in the code below that the text is centred horizontally, the text color is different and that there’s no additional space between letters:

That code creates the “Thank you!” message, but it doesn’t add the line above it. Therefore, let’s make an addition to that method:

Now we have a horizontal line as well!

Before reaching the end of this part, there’s one observation I’d like to make regarding both header and footer. If you notice, you’ll see that both texts are NSString, and not String objects. There’s a specific reason for that: The drawAtPoint(...) method that performs the real drawing belongs to the NSString class. If you want to use a String object instead, then you have to cast it to NSString as follows:

Run the app again now and preview the exported PDF; this time contains a header and a footer.

Bonus Part: Previewing and Emailing the PDF

At this point we have already reached to the end of the main concept of this post. However, if you run the demo app on a device there’s no straight way to see the exported PDF (you can do it actually through Xcode but it’s a hassle to do it every time you create a PDF), so we’ll add two extra features in our app: The capability to preview the PDF in the web view that already exists in PreviewViewController, and to send it via email. We’ll allow the user to select the final action by presenting an alert controller with all the possible options. We won’t stick too much to the details, as any code presented here is out of the scope of the tutorial.

Our work will take place in the PreviewViewController.swift file, so find it in the Project Navigator and open it. Add the following new method that displays the alert controller to the screen:

The actions for each option are not defined yet, so let’s do that now. For the preview action we’ll load the PDF file from the file into the web view using a NSURLRequest object as shown below:

For sending the email, let’s create a new method that will prepare it and attach the file to it:

Don’t forget to go to the top of the file and add the following line so you can use the MFMailComposeViewController:

Back to the showOptionsAlert() method, let’s complete the actionPreview action as shown in the next snippet:

We’re almost done, as there’s just one thing still missing. We must call the showOptionsAlert() method. The alert controller should be presented to the user right after the PDF file has been exported to the documents directory, therefore go to the exportToPDF(...) IBAction method and add the single line shown below:

That’s all! You can now run the app on a device and interact with the exported PDF file.

t54_10_after_export_alert

Summary

No matter what techniques currently exist or will exist in the future for creating PDF documents, the method presented in this post will always be a standard, flexible and secure way to render PDF files. It’s suitable for the most of the cases, with one drawback only: The need of HTML templates that will be used to generate the real content. For me, this is a low price for what we get as a payback. I strongly believe that it’s way more preferable to mess with HTML code, placeholders and string replacements than taking the long way of manually drawing a PDF. Other than that, the real drawing to PDF code is quite standard, and with a few tweaks to the demo app’s code you can have great results. In any case, I hope you like the technique described in this post and you actually use it in your projects. Thanks for reading, and happy printing to PDF documents!

For reference, you can refer to the complete Xcode project on Github.com.

iOS
A Beginner’s Guide to Presentation Controllers in iOS 8
iOS
Introduction to Firebase: Building a Simple Social App in Swift
iOS
Building a Text to Speech App Using AVSpeechSynthesizer
  • NaturalFit Mdh

    Hello, i have a bug here it does not work: i have this message « call can throw, but is is not marked with ’try’ and the error is not handled» what does it mean? i change the code because initially i have a bug who tell me that does not possible to convert a String To URL. I’m code with swift 3.0.

    [11:58]

    func exportHTMLContentToPDF(_ HTMLContent: String) {

    let printPageRenderer = CustomPrintPageRenderer()

    let printFormatter = UIMarkupTextPrintFormatter(markupText: HTMLContent)

    printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)

    let pdfData = drawPDFUsingPrintPageRenderer(printPageRenderer)

    // pdfFilename = “(AppDelegate.getAppDelegate().getDocDir())/Invoice(invoiceNumber).pdf”

    // pdfData?.write(to: pdfFilename, options: true)

    let pdfFilename = “(AppDelegate.getAppDelegate().getDocDir())/Invoice(invoiceNumber).pdf”

    let pdfURL = URL(string: pdfFilename)

    pdfData?.write(to: pdfURL!, options: true)

    print(pdfURL)

    }


  • Xian Teriosa

    Hello Mr. Grabiel, i’m just a newbie in swift and i found this tutorial of yours very interesting. So I’ve tried to do it but the image for the logo doesn’t appear in the pdf.

    And I also tried to change the codes in starter pack to implement editing feature for the INVOICES list and ITEMS list using the methods from the apple tutorial “Starting Developing IOS Apps(Swift)” but i’ve got problem with segue and ITEMS list table because the ITEMS list always show same ITEMS for every INVOICES.

    I hope you understand it.

    Can you please help me with this? Can you create a starter pack with editing feature and persist data? Or can you just give me a link where I can learn this stuff? Thank you in advance! 🙂

    Hoping for your response. 🙂


    • Tatha

      TathaTatha

      Author Reply

      Hi Gabriel – Thanks for the lovely tutorial.

      Xian – I was following this tutorial to generate a PDF (using HTML) and had issues with Images. The images were not seem to be loading initially.

      However I observed that the tutorial first loads the HTML into the WebView after which it the user has to click on the PDF button.

      I tried the same approach and it worked!!

      I was initially trying to load the PDF from the HTML String directly without previewing the HTML.

      Hope this helps


  • Vince

    VinceVince

    Author Reply

    Hello, thank you for the tutorial!

    One question, if I try to generate the PDF file without previewing the HTML in WebView, then the PDF file would not have the logo image.

    Anyway to solve this?

    Thanks in advance!


  • Vikas

    VikasVikas

    Author Reply

    Hi,

    Thanks it is really nice tutorial, very well explained. I have one problem, I am not able to see the preview of the PDF file. I am able to send it as an email, or I can see it in Finder. But when I press preview nothing happesn. Could you please help me with this.


    • Vikas

      VikasVikas

      Author Reply

      I think I found the problem, its in following line

      pdfFilename = “(AppDelegate.getAppDelegate().getDocDir())/Invoice(invoiceNumber).pdf”

      invoiceNumber is String, so no need to put in( ). It should be like following

      pdfFilename = “(AppDelegate.getAppDelegate().getDocDir())/Invoice” + invoiceNumber + “.pdf”


  • Lionel Smith

    Hello Gabriel,

    Great tutorial, but:

    1. The mail composer does not get dismissed.

    To fix this:

    a) change the class definition to
    class PreviewViewController: UIViewController, MFMailComposeViewControllerDelegate

    b) change function sendEmail() to

    func sendEmail() {
    if MFMailComposeViewController.canSendMail() {
    let mailComposeViewController = MFMailComposeViewController()
    mailComposeViewController.mailComposeDelegate = self
    mailComposeViewController.setSubject(“Invoice”)
    mailComposeViewController.addAttachmentData(NSData(contentsOfFile: invoiceComposer.pdfFilename)! as Data, mimeType: “application/pdf”, fileName: “Invoice”)
    present(mailComposeViewController, animated: true, completion: nil)
    }
    }

    so that the mailComposeViewController becomes the mailComposeDelegate

    then add the following delegate method

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
    controller.dismiss(animated: true, completion: nil)
    }

    2. If there are more that 8 line items in the invoice the pdf gets truncated to only show the first 8 line items.

    any suggestions

    Thanks Lionel


  • David Reinhold

    Hi,

    Brilliant tutorial. I’m having a problem with getting my images to show up in the PDF though… They show up in in the UIWebview but not in the pdf. To verify, I downloaded the sample project and got the same result there. Any ideas?


    • Vinicius Vieira

      Hey man, I’m having the same issue. Did you managed to fix it? Thanks


    • Edwin Wiersma

      Did you manage to solve this?


  • Tim Vogt

    Tim VogtTim Vogt

    Author Reply

    any way to see a finished project? Im having a hard time with the invoice html and “Let’s start implementing that method.” Is this a task or an explanation? I want tot build a checklist with saves the files combined with photo’s made with the camera and pdf savend or emails to client …


  • RussGum

    RussGumRussGum

    Author Reply

    Great tutorial but I can’t get the mail window to go away once I send an email. How do I do this?


  • Tse Wai Lun

    Hi, thank you for your tutorial . I want to know how to make the signature function for the PDF


  • Rick

    RickRick

    Author Reply

    I can run the app in the simulator, but when i try to run on my device i get this message : 2016-12-01 19:13:39.070192 Print2PDF[4693:1374229] fatal error: unexpectedly found nil while unwrapping an Optional value

    What’s wrong?


    • Ivan Sinigaglia

      Me too. I am trying to fix this nil value but nothing till now.


    • Edwin Wiersma

      Did you manage to solve this problem?


  • kevin

    kevinkevin

    Author Reply

    great tutorial!!
    but i need 2 pages. can some one help me with printing to 2 pages with this implementation?


  • Guest

    GuestGuest

    Author Reply

    The best tutorial for printing in PDF


  • Sam Rulez

    Sam RulezSam Rulez

    Author Reply

    Hello Gabriel,
    I need to print html page comprising of 3 pages. And above tutorial only prints first page of the PDF. Can you please share the code or explain how to “include the beginning of the PDF page creation and the print page renderer drawing into a loop”, to achieve this.


  • Sam Rulez

    Sam RulezSam Rulez

    Author Reply

    Any Help from anyone Please !!!
    I need to print html page comprising of 3 pages. And above tutorial only prints first page of the PDF. Can you please share the code or explain how to achieve this.


  • Ivan Sinigaglia

    Is this tutorial for Swift 3?


  • Nico

    NicoNico

    Author Reply

    I can’t display the image either in the HTML renderer on the PDF file.
    Here is the source of my image in the image tag:

    Any idea why?


  • James Buckley

    Has anybody managed to get this to move onto a new page??


  • Humberto

    HumbertoHumberto

    Author Reply

    Great Tutorial. You will have to make small adjustments for it to work on Swift 3 but nothing major.


  • Jerry Ray Matthew Raby

    How would you print to landscape? Swapping height/width did not work for me 🙁


  • Rain

    RainRain

    Author Reply

    Great tutorial!

    Is it true that using “UIPrintPageRenderer” in your app will make it rejected in app store review?


  • Naveen R

    Naveen RNaveen R

    Author Reply

    In case anyone is looking to print multiple pages, this is the code.

    for i in 0..<printPageRenderer.numberOfPages {
    UIGraphicsBeginPDFPage()
    printPageRenderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
    }


  • Ricardo Martins

    Nice


    • Jitendra

      JitendraJitendra

      Author Reply

      Hi Ricardo, Did you found solution for the same?


      • Ricardo Martins

        Sorry, I did not do any more tests because I did not have to implement any projects yet.


        • Jitendra

          JitendraJitendra

          Author Reply

          Ok thanks, But i got solution for the same.
          It works for me-
          class CustomPrintPageRenderer: UIPrintPageRenderer {
          let A4PageWidth: CGFloat = 595.2
          let A4PageHeight: CGFloat = 841.8
          override init() {
          super.init()
          let pageFrame = CGRect(x: 0, y: 0, width: A4PageWidth , height: A4PageHeight )
          self.setValue(pageFrame, forKey: “paperRect”)
          self.setValue(CGRect(x: 0, y: 0, width: A4PageWidth , height: A4PageHeight – 50), forKey: “printableRect”)
          self.footerHeight = 50.0
          self.headerHeight = 50.0
          }
          }


          • Ricardo Martins

            Nice Jitendra!! Thank you for your contribution. I’ll save this code


  • Sedeek Mohamed Reda

    Hello sir,i hope that you are fine,
    i want to display image on html file i am trying to send it from UIImageView but unfortunately no displaying for it,could you help please.but you should know that when i give it a pass(Bundle.main.path(forResource: “Sedeek”, ofType: “jpg”)) it works.
    Thanks


  • Shirish Bankar

    Can someone help me with this : https://stackoverflow.com/q/51419865/2563535
    The above tutorial works but in my case, my HTML has tables and stylesheet . It doesnt load properly in UIWebview.


  • Sebastian

    SebastianSebastian

    Author Reply

    How do I print in landscape format?


  • jjstyle0427

    is possible export complete PDF without loading html?
    When I exported PDF without loading html, the image could not display


    • Alper Yalcin

      You should delay exporting of pdf at least 1 second after displaying it as html in webview. You can use this code as example:
      webView.loadHTMLString(invoiceHTML, baseURL: nil)
      HTMLContent = invoiceHTML
      // To delay execution fo the code
      DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // change 1 to desired number of seconds
      // Code with delay

      self.exportToPDFAndDisplay()
      }


Leave a Reply to Jerry Ray Matthew Raby
Cancel Reply

Shares