How To Create a PDF with Quartz 2D in iOS 5 – Part 1
This is a blog post by iOS Tutorial Team member Tope Abayomi, an iOS developer and Founder of App Design Vault, your source for iPhone App Design. Sometimes in your apps you might want to generate a PDF with data from the app for your users. For example, imagine you had an app that allowed […] By Tope Abayomi.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Create a PDF with Quartz 2D in iOS 5 – Part 1
15 mins
This is a blog post by iOS Tutorial Team member Tope Abayomi, an iOS developer and Founder of App Design Vault, your source for iPhone App Design.
Sometimes in your apps you might want to generate a PDF with data from the app for your users. For example, imagine you had an app that allowed users to sign a contract – you would want the users to be able to get a PDF with the final result.
But how do you generate a PDF programmatically? Well, it’s easy to do in iOS with the help of Quartz2D!
In this tutorial series, you’ll get hands-on experience with creating a simple PDF with Quartz2D. The PDF we’ll make will be for an invoice-making app, as you can see in the screenshot.
This tutorial assumes you are familiar with the basic new features in iOS 5 such as Storyboards and ARC. If you are new to iOS 5, check out some of the other iOS 5 tutorials on this site first.
Getting Started
Run Xcode and create a new project with the iOS\Application\Single View Application template. Enter PDFRenderer for the project name, choose iPhone for the Device Family, make sure Use Storyboard and Use Automatic Reference Counting are checked, and finish creating the project.
We are going to use two screens in this project. The first will simply have a button that will show the PDF when tapped. The second screen will be the PDF itself.
Select the MainStoryboard.storyboard file. In the main window, you will see a View Controller. We need this View Controller to be embedded in a Navigation Controller to start with, so click on the Editor Menu, then select Embed In/Navigation Controller.
The View Controller now has a segue from the Navigation Controller.
Now drag a UIButton from the objects tab to the View Controller, and rename the label “Draw PDF.”
If you run the application, you should see a simple View with a button displayed that says “Draw PDF,” but does nothing when tapped. We’ll take care of that shortly.
Now let’s add the second View that will hold the PDF.
Drag a new View Controller from the objects tab onto the Storyboard. Ctrl+Drag the “Draw PDF” button onto the new View Controller. When you release the mouse, you should see a popup similar to the one in the image below.
Select the Push option. This will create a segue onto the new View Controller so that it is displayed when the button is tapped. In other words, our button is now functional!
Run the application, tap the button, and you should see an empty View Controller pushed onto the screen. Storyboards rule!
Creating the PDF and Drawing Text
Now that we have the framework for our PDF, we’re ready to write some code.
Before we do that, select File\New\New File to add a new file to the project. Choose the iOS\Cocoa Touch\UIViewController subclass template, enter PDFViewController for the Class and UIViewController for the Subclass, make sure “With XIB for user interface” is NOT checked, and finish creating the file. We do not need a nib because we will use the View Controller created in the storyboard.
Connect the last View Controller we created to this new file by selecting the View Controller on the Storyboard and changing the class to PDFViewController in the identity inspector.
To draw some text in our PDF, we’re going to need to use the Core Text framework. To do this, select the PDFRenderer target and go to the Build Phases tab. Click the + sign below the Link Binaries With Libraries option, and then select the CoreText framework.
Then open PDFViewController.h and import the CoreText header:
#import <CoreText/CoreText.h>
OK time for the code! Add a new method to PDFViewController.m to create a “hello world” PDF. This is a long method, but don’t worry – we’ll explain it bit by bit afterwards.
-(void)drawText
{
NSString* fileName = @"Invoice.PDF";
NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];
NSString* textToDraw = @"Hello World";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
CGRect frameRect = CGRectMake(0, 0, 300, 50);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
// 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);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
}
The first six lines create a PDF filename for a file that will reside in the Documents folder. The file will be called Invoice.pdf.
NSString* fileName = @"Invoice.PDF";
NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];
The next block of code creates a “Hello world” string that we will draw onto the PDF. It also converts the string to its CoreGraphics counterpart, CFStringRef.
NSString* textToDraw = @"Hello World";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
Now we create a CGRect that defines the frame where the text will be drawn.
CGRect frameRect = CGRectMake(0, 0, 300, 50);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
Next we create a PDF context and mark the beginning of a PDF page. Each page of the PDF has to start with a call to UIGraphicsBeginPDFPageWithInfo.
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
The coordinates of Core Graphics drawings start from the bottom-left corner, while UIKit global coordinates start from the top-left. We need to flip the context before we begin drawing.
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);
Then we draw the actual frame with the text, release all the Core Graphics objects, and close the PDF context (hence writing the PDF to disk).
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
If you are interested in learning more about how Core Text works and some more cool things you can do with it, check out our Core Text tutorial.