report

Create PDF using iOS UIKit

Source

The iOS SDK offers two venues for creating PDF files: UIKit or the Core Graphics. With either of these paths you can create one page or loop over the content and create multiple pages. These drawn PDF pages can include custom formatting, images, borders and page numbers to name a few. But you are only limited by your own imagination and the time allowed for the project or the requirements or a combination of any of these to produce beautiful looking PDF documents that can be printed or stored in iCloud, Dropbox or Google Drive for example.

In this tutorial I will review the PDF creation lifecycle and I will build a simple iOS iPhone app to capture text from an UITextView and save that content to a PDF file using the UIGraphicsBeginPDFContextToFile and some classes from the Core Text framework. Finally I will walk you through the code.The UIKit has two methods for creating PDF documents: UIGraphicsBeginPDFContextToData and UIGraphicsBeginPDFContextToFile. The first will draw PDF content on a page that will be stored in a NSMutableData.

The process of creating PDF documents that draws content on the page is very straightforward. First you need to set the page size and create the PDF Graphic context with the UIGraphicsBeginPDFContextToFile method. To set the page dimensions, you can user the CGRect class and the CFRange class in the Core Foundation framework. Render the contents on the page by first marking the beginning of the page using the UIGraphicsBeginPDFPageWithInfo function. Once the PDF drawing process is complete, you need to call the UIGraphicsEndPDFContext to close the file saving the the contents in the PDF file. The lifecycle is presented in the following screenshot.

UIKit PDF creation lifecycle
UIKit PDF creation lifecycle | Source
Storyboard layout for the iOS iPhone app
Storyboard layout for the iOS iPhone app | Source

Develop the Application : Storyboard

To demonstrate how to create PDF documents using the functions in the UIKit, I will create a Single View application using the provided template in Xcode. You may opt to use a xib file to setup your user interface (GUI) in the Interface Builder (IB), I am suggesting to leave the xib option unchecked and Xcode will create a storyboard instead. I prefer working with storyboards because you to build your complete UI workflow in one screen. This is pretty unique amongst IDEs and development platforms. You can name your app anything you like, however I will name mine “PDF Creator”. Once the app is loaded in Xcode IDE, open the storyboard add:

  • An UITextField and corresponding UILabel,
  • then add an UITextView and corresponding UILabel, finally
  • add an UIButton and change the front facing label to “Create PDF”

The following screenshot provides a visual of this simple UI. Next I will create IBOutlets and IBAction for these visual controls in the associated header file.

Create IBOutlets to capture text to create the PDF content
Create IBOutlets to capture text to create the PDF content | Source
Create IBAction to generate PDF
Create IBAction to generate PDF | Source

To create the IBOutlets and IBAction, click on the Editor assistant icon in the toolbar menu (it is the one that looks either like a face or a tuxedo depending how you process images). The header file of the custom view controller class as defined in the “Identity inspector” (it is the third icon from the left in the utilities window on the right. If it is not open, click on the “Show utilities” icon in the toolbar. It is the one before the Organizer icon). Using the keyboard and the mouse, select the UITextField and control + drag a connection line from the UITextField to the open header file. Releasing the buttons will trigger Xcode to produce a popover, allowing you to define a name for the IBOutlet, like “enterSubject” which I will use for the filename. The remaining options, like the connection type, will remain the same. the following screenshot provides a visual on the process.

Repeat the process for the UITextView, naming the connection “enterText”. When creating a connection for the UIButton which will be an IBAction, change the connection type in the popover to “Action”. I will call this IBAction, createPDF. Xcode will create the correponding method in the header and add the method template to the implementation. To complete the UI, drag a connection from both the UITextField and the UITextView to the proxy icon at the bottom of the View Controller and select “delegate” from the popover. We will need this call the textFieldShouldReturn to dismiss the keyboard after editing the UITextField.

Develop the PDF Processing Logic

The final part of the app is to add logic to the header and implementation files of the ViewController custom class to grab the text from the fields in the storyboard to create a PDF file.

Open the header file and add two extra instance methods to draw the page number on each page of the PDF and to draw the text on the each page of the PDF as required. For the page number, define the drawPageNbr method with one argument of type int called pageNumber. The second method will be called updatePDFPage and it will have two arguments, setTextRange which will be the text from the UITextView and the setFramesetter to set the text in a frame on the page, sort of like a layout manager. The code is provided below.

ViewController.h

//
//  ViewController.h
//  PDF Creator
//
//  Created by Kevin Languedoc on 11/17/12.
//  Copyright (c) 2012 Kevin Languedoc. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>

@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UITextField *enterSubject;
@property (strong, nonatomic) IBOutlet UITextView *enterText;
- (IBAction)createPDF:(id)sender;

-(void)drawPageNbr:(int)pageNumber;
-(CFRange*)updatePDFPage:(int)pageNumber setTextRange:(CFRange*)pageRange setFramesetter:(CTFramesetterRef*)framesetter;

@end

textFieldShouldReturn

In the implementation file the method to look at is the textFieldShouldReturn. This method is called by the UITextField delegate to dismiss the keyboard when the “Return” is pressed on the keyboard. In the method’s body, the code checks to see if the caller is the enterSubject UITextField and calls the resignFirstResponder if it is.

shouldChangeTextInRange:replacementText
The second method that is needed to dismiss the keyboard is textView :shouldChangeTextInRange:replacementText. This method is called by the UITextView by its delegate in a similar fashion.

createPDF
Next we will discuss the createPDF IBAction method that we created earlier. Since we need to save the PDF files to the Documents directory in the application’s sandbox, we will first get a handle on the Documents path using the NSSearchPathForDirectoriesInDomains and passing in the NSDocumentDirectory constant for the Documents directory. This will return an array of directories which will be used to get the top element of the array with the objectAtIndex:0 for the top level Documents directory. Next append the name of the of the file which will come from the enterSubject UITextField. Then create the frame for the text from the UITextView using the CFAttributedStringRef that will setup the CFString using the CFAttributedStringCreate function. This is to convert the NSString to the CFString to be used by the Core Foundation class. Notice the __bridge directive which is used in conjunction with ARC to provide a casting between the string object the core foundation function.

If this goes according to plan. setup the framesetter object using the CTFramesetterCreateWithAttributedString function and begin the PDF creation process by defining a PDF context with the UIGraphicsBeginPDFContextToFile passing it the value documentPath to create the initial PDF file on the file system.

Then add a page to the file using the UIGraphicsBeginPDFPageWithInfo method. Set the size of the page to 612x792. Then set the page number and call the drawPageNbr to draw the page number, defined by currentPage int variable which will be increment with each iteration of the do loop. Then following the PDF creation lifecycle, add the text that we set in the framesetter earlier to the page using the updatePDFPage method. I will describe this method next after this one method. If we have reach the end of the text, set the done variable and exit the loop and call the UIGraphicsEndPDFContext to close the PDF context. Finally release the Core Foundation CTFramesetterRef since ARC doesn’t manage the Core Text classes like Core Foundation.

ViewController.m

//
//  ViewController.m
//  PDF Creator
//
//  Created by Kevin Languedoc on 11/17/12.
//  Copyright (c) 2012 Kevin Languedoc. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController
@synthesize enterSubject;
@synthesize enterText;

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidUnload
{
    [self setEnterSubject:nil];
    [self setEnterText:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(BOOL)textFieldShouldReturn:(UITextField *)textfield{
    if(textfield == self.enterSubject){
        [textfield resignFirstResponder];
    }
    return YES;
}

- (IBAction)createPDF:(id)sender {
    
    //Get Document Directory path
    NSArray * dirPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    
    //Define path for PDF file
    NSString * documentPath = [[dirPath objectAtIndex:0] stringByAppendingPathComponent:enterSubject.text];

    // Prepare the text using a Core Text Framesetter.
    CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, (__bridge CFStringRef)enterText.text, NULL);
    if (currentText) {
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
        if (framesetter) {
            
           
            // Create the PDF context using the default page size of 612 x 792.
            UIGraphicsBeginPDFContextToFile(documentPath, CGRectZero, nil);
            
            CFRange currentRange = CFRangeMake(0, 0);
            NSInteger currentPage = 0;
            BOOL done = NO;
            
            do {
                // Mark the beginning of a new page.
                UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
                
                // Draw a page number at the bottom of each page.
                currentPage++;
                [self drawPageNbr:currentPage];
                
                // Render the current page and update the current range to
                // point to the beginning of the next page.
                currentRange = *[self updatePDFPage:currentPage setTextRange:&currentRange setFramesetter:&framesetter];
                
                // If we're at the end of the text, exit the loop.
                if (currentRange.location == CFAttributedStringGetLength((CFAttributedStringRef)currentText))
                    done = YES;
            } while (!done);
            
            // Close the PDF context and write the contents out.
            UIGraphicsEndPDFContext();
            
            // Release the framewetter.
            CFRelease(framesetter);
            
        } else {
            NSLog(@"Could not create the framesetter..");
        }
        // Release the attributed string.
        CFRelease(currentText);
    } else {
        NSLog(@"currentText could not be created");
    }
}


drawPageNbr
The drawPageNbr method first sets the string and page number that will be drawn at the bottom of the page. The next line of code establishes the font of 10 pts. Then we set the total size of the area where the page number will be drawn, which our case is 612x72. The pageStringSize will contain the maximum size of the page number string will be drawn by determining which font to use and calculating the size constraints with constrainedBySize, which is the bounds of the maxSize rectangle and also sets the line break mode to use. This will be used next in the stringRect variable to establish the size of the rectangle to use and its placement on the page. The final statement actual draws the page number on the page

ViewController.m - drawPageNbr

-(void)drawPageNbr:(int)pageNumber{
    NSString *setPageNum = [NSString stringWithFormat:@"Page %d", pageNumber];
    UIFont *pageNbrFont = [UIFont systemFontOfSize:14];
  
    CGSize maxSize = CGSizeMake(612, 72);
    CGSize pageStringSize = [setPageNum sizeWithFont:pageNbrFont
                                   constrainedToSize:maxSize
                                       lineBreakMode:UILineBreakModeClip];
    
    CGRect stringRect = CGRectMake(((612.0 - pageStringSize.width) / 2.0),
                                   720.0 + ((72.0 - pageStringSize.height) / 2.0),
                                   pageStringSize.width,
                                   pageStringSize.height);
    [setPageNum drawInRect:stringRect withFont:pageNbrFont];
}

ViewController.m - updatePDFPage

This last method will draw the content of the UITextView in the rectangle of the PDF page. First we get a handle on the graphics context that we are using. Then set the rectangle size for the text that will be use by the frame. The frameset will set the bounds of the text layout. The CGPathCreateMutable create a graphics path which represents a series of lines and shapes. These two preceding variables will then be used to create the rectangle for the text with the CGPathAddRect function.

The CTFramesetterCreateFrame function uses the text and framesetter to layout the content in the frame. The CGContextTranslateCTM and CGContextScaleCTM functions position the text within the frame and the CTFrameDraw draws the text in the frame. The CTFrameGetVisibleStringRange returns the number of characters that fit in a frame and the characters that don’t fit one frame can be moved to another frame. This is it. This code, presented below, offers one of several ways of creating PDFs, even within the UIKit framework and Core Graphics. The use of the Core Text is optional but essential for this example.

ViewController.m - updatePDFPage


-(CFRange*)updatePDFPage:(int)pageNumber setTextRange:(CFRange*)pageRange setFramesetter:(CTFramesetterRef*)framesetter{
    // Get the graphics context.
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    // Put the text matrix into a known state. This ensures
    // that no old scaling factors are left in place.
    CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
    // Create a path object to enclose the text. Use 72 point
    // margins all around the text.
    CGRect frameRect = CGRectMake(72, 72, 468, 648);
    CGMutablePathRef framePath = CGPathCreateMutable();
    CGPathAddRect(framePath, NULL, frameRect);
    // Get the frame that will do the rendering.
    // The currentRange variable specifies only the starting point. The framesetter
    // lays out as much text as will fit into the frame.
    CTFrameRef frameRef = CTFramesetterCreateFrame(*framesetter, *pageRange,
                                                   framePath, NULL);
    CGPathRelease(framePath);
    // Core Text draws from the bottom-left corner up, so flip
    // the current transform prior to drawing.
    CGContextTranslateCTM(currentContext, 0, 792);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    // Draw the frame.
    CTFrameDraw(frameRef, currentContext);
    // Update the current range based on what was drawn.
    *pageRange = CTFrameGetVisibleStringRange(frameRef);
    pageRange->location += pageRange->length;
    pageRange->length = 0;
    CFRelease(frameRef);
    return pageRange;
}
@end

© 2012 Kevin Languedoc

More by this Author


Comments 28 comments

Iva 3 years ago

Hallo Kevin, thank you for a great tutorial... Is it possible to create a PDF file from UITableView? Thank you, Iva


klanguedoc profile image

klanguedoc 3 years ago from Canada Author

Yes, a UITableView is only a data display for its data source, so to create a PDF from data displayed in an UTableView in whole or in part, use the the data source. To create a PDF from selected data, use the UITableView delegate's method – tableView:didSelectRowAtIndexPath: and add the selected data to a new NSMutableArray


Iva 3 years ago

Thank you for your answer, Kevin!


klanguedoc profile image

klanguedoc 3 years ago from Canada Author

hope it helps


mini 3 years ago

Nice tutorial ,Thanks ...How easy it is to add images on top of this PDF and do operations like rotate ,scale on them ?


brunohdc 3 years ago

Thank for your tutorial, its very useful to me.


klanguedoc profile image

klanguedoc 3 years ago from Canada Author

Mini, it's all there in the API. Yes you can add images. It is basically drawing and layout your page.


jfellows123 3 years ago

First, I would like to thank you for you very thoughtful tutorials. I'm a new coder, and have found them invaluable. I'm wondering about this iOS Create PDF using UIKit. Is there some code missing? It seems like the code related to ":shouldChangeTextInRange:replacementText" is missing. When you run this as is, the keyboard won't retract. Would it be possible to get an update with that code or the code itself. I'm really tying to learn how to create, view, and print out PDFs from a view of text and images (like a photo album). All the code examples I have found are done with xib. My project is developed with storyboards, and I can't seem to convert the xib to storyboards. Your's was the first storyboard version I have found. Still looking for storyboard PDF viewing and printing examples. Any help would be appreciated. Jack


jfellows123 3 years ago

My apologies. The code works. I forgot to create the delegate connections for the TextField. However, I am getting a warning that "UILineBreakClip is depreciated: first deprecated in iOS 6.0" in the drawPageNbr section. Can you suggest a fix for this? Also I'd like to add a photo to this and be able to print it out. Any suggestions much appreciated. Jack


klanguedoc profile image

klanguedoc 3 years ago from Canada Author

Cool...

For the photo, you will need to use UIImage and NSData to convert image to binary. For the UILineBreakClip, use the NSLineBreakMode instead.


mjacobs 2 years ago

Thanks for the great tutorial. I tried running this and found that some of the code was a little old (it stated it was depricated in iOS 7). Is there any way you can update the code?


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

mjacobs

Sure thing. I will update by sunday 12/22/2013. Hope this is ok.


mjacobs 2 years ago

That's Awesome! A huge help. I've searching the web for an update for that code since I posted 11 hours ago! I can wait and save my sanity!

Your the Man!


harsh 2 years ago

thanks for tutorial ,but it take too much memory can we compress the result pdf file ??


iyke okeke 2 years ago

Thanks for the tutorial but is there a way to download this source files. Can't really see it there.And another thing, the area where you added the do/while is generating error.

Thanks


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

harsh

If you are using images in your files, you might consider compressing the images first.

iyke okeke

you can get all source at www.iosdev101.com/downloads


B Bhangu 2 years ago

HIII how can i download source code of this?


klanguedoc profile image

klanguedoc 2 years ago from Canada Author

you can get the source at www.iosdev101.com/downloads


Pradeep 20 months ago

Hi,

I am new to iOS development.i made registration page.and also use validation i want to our name,pas textfiled and also use picker view for male and feamle for catogry.i want to all data send in one view to another view and also print data in tableview.please help me how to make this


klanguedoc profile image

klanguedoc 20 months ago from Canada Author

pradeep

Please check out my other tutorials that explain how to do this

http://hubpages.com/@klanguedoc


Geo 18 months ago

Can you put the source code,please ? i get this :CGContextScaleCTM: invalid context 0x0. and nothing is happen when create pdf is pressed


klanguedoc profile image

klanguedoc 18 months ago from Canada Author

Sorry but I don't have the source code. I lost the code when the HDD went down and I thought I had a backup but I don't. Sorry.

Is the Context (current session) set correctly. Also what iOS version are you using


Bikram 9 months ago

Hello Kevin, Thanks a lot for great toturials. It is very helpful for me. But I can't understand how I can save whole tableview data into PDF file. Please help me. Thanks and Regards...


klanguedoc profile image

klanguedoc 9 months ago from Canada Author

Hi

You need to use the data source of the UItableview and not the table view itself as the source for your pdf. If your data source is a nsarray, etc. Then this is the data that you can save as pdf


Bikram 9 months ago

Hello Klanguedoc. Thanks for your response. I can understand that how I can make PDF format as like table and how I can save all values according to table coloums and row. Please if you have any tutorials and anything else she with me thanks a lot....


Ryan 9 months ago

Hello Kevin, I have a quick question. I have a textView with long content on my viewcontroller. It is about 4 pages long. I have two problems.

1) Having a problem with converting obj-c to swift. Do you have swift code for this tutorial?

2) Which part is making sure that it correctly renders all pages of textview??


Bikramjeet Singh 8 months ago

Hi Klanguedoc, How i can show above mention PDF into webview. Plz help me. thanks


klanguedoc profile image

klanguedoc 8 months ago from Canada Author

Bikram

If I understand correctly, you would like to save all data from table to pdf? I can help with this

Ryan

Sorry I don't have any code at the moment to help you out.

Bikramjeet

Rendering a pdf in a browser (webview) is very much dependent on the browser the end user is using and not the webview itself.

    Sign in or sign up and post using a HubPages Network account.

    0 of 8192 characters used
    Post Comment

    No HTML is allowed in comments, but URLs will be hyperlinked. Comments are not for promoting your articles or other sites.


    Click to Rate This Article

    Menu

    Explore