How To Create a PDF with Quartz 2D in iOS 5 – Part 2

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. Welcome to the second part of the tutorial series on how to generate a PDF using Quartz 2D! In Part One, we set up an app framework for […] By Tope Abayomi.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Contents

Hide contents

Adding the Logo

Next, open InvoiceView.xib and add a UIImageView to the upper right for the logo:

Then add this new method into PDFRenderer.m (right before drawPDF):

 +(void)drawLogo
 {    
    NSArray* objects = [[NSBundle mainBundle] loadNibNamed:@"InvoiceView" owner:nil options:nil];
    
    UIView* mainView = [objects objectAtIndex:0];
    
    for (UIView* view in [mainView subviews]) {
        if([view isKindOfClass:[UIImageView class]])
        {            
            UIImage* logo = [UIImage imageNamed:@"ray-logo.png"];
            [self drawImage:logo inRect:view.frame];
        }
    }    
 }

This loads the UIImageView from the Xib file in the same way we load the labels. Then it draws the logo onto the PDF using the coordinates of the UIImageView.

Finally, call this method after drawLabels in the drawPDF method:

 +(void)drawPDF:(NSString*)fileName
 {
    // Create the PDF context using the default page size of 612 x 792.
    UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
    // Mark the beginning of a new page.
    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
    
    [self drawText:@"Hello World" inFrame:CGRectMake(0, 0, 300, 50)];
    
    [self drawLabels];
    [self drawLogo];
    
    // Close the PDF context and write out the contents.
    UIGraphicsEndPDFContext();
 }

Run the application in the simulator, and you’ll see the logo in the top right corner just as we specified!

Pretty cool how easy it is to visually lay out the PDF with the aid of UIView coordinates, eh?

Drawing the Table

It’s time to add the table that will hold all the invoice information. Our table will be nothing more than an arrangement of vertical and horizontal lines.

In this case, we won’t use the InvoiceView. Instead we’ll use a series of variables like the table height and width, and the row height and column width.

Add the following method to PDFRenderer.m (right before drawPDF):

 +(void)drawTableAt:(CGPoint)origin 
    withRowHeight:(int)rowHeight 
   andColumnWidth:(int)columnWidth 
      andRowCount:(int)numberOfRows 
   andColumnCount:(int)numberOfColumns

 {   
    for (int i = 0; i <= numberOfRows; i++) 
    {        
        int newOrigin = origin.y + (rowHeight*i);
                
        CGPoint from = CGPointMake(origin.x, newOrigin);
        CGPoint to = CGPointMake(origin.x + (numberOfColumns*columnWidth), newOrigin);
        
        [self drawLineFromPoint:from toPoint:to];
    }
 }

This method draws our horizontal lines. At the beginning of the method, we pass in the values for the starting position of the table, the number of rows and columns, the height of each row and the width of each column.

The loop then goes through the motions for each row, calculating where the row should start and where it should end. Finally, the drawLine:from:to method is called to draw the line. We then add the following code to the drawPDF method:

 +(void)drawPDF:(NSString*)fileName
 {
    // Create the PDF context using the default page size of 612 x 792.
    UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
    // Mark the beginning of a new page.
    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
    
    [self drawText:@"Hello World" inFrame:CGRectMake(0, 0, 300, 50)];
    
    [self drawLabels];
    [self drawLogo];
    
    int xOrigin = 50;
    int yOrigin = 300;
    
    int rowHeight = 50;
    int columnWidth = 120;
    
    int numberOfRows = 7;
    int numberOfColumns = 4;
    
    [self drawTableAt:CGPointMake(xOrigin, yOrigin) withRowHeight:rowHeight andColumnWidth:columnWidth andRowCount:numberOfRows andColumnCount:numberOfColumns];

    // Close the PDF context and write the contents out.
    UIGraphicsEndPDFContext();
 }

Run the application in the simulator and you should see the horizontal lines on the PDF.

The next step is to draw the vertical lines. Add another loop to the drawTable method below the first loop:

 +(void)drawTableAt:(CGPoint)origin 
    withRowHeight:(int)rowHeight 
   andColumnWidth:(int)columnWidth 
      andRowCount:(int)numberOfRows 
   andColumnCount:(int)numberOfColumns

 {   
    for (int i = 0; i <= numberOfRows; i++) 
    {        
        int newOrigin = origin.y + (rowHeight*i);
        
        CGPoint from = CGPointMake(origin.x, newOrigin);
        CGPoint to = CGPointMake(origin.x + (numberOfColumns*columnWidth), newOrigin);
        
        [self drawLineFromPoint:from toPoint:to];        
    }
    
    for (int i = 0; i <= numberOfColumns; i++) 
    {        
        int newOrigin = origin.x + (columnWidth*i);
                
        CGPoint from = CGPointMake(newOrigin, origin.y);
        CGPoint to = CGPointMake(newOrigin, origin.y +(numberOfRows*rowHeight));
        
        [self drawLineFromPoint:from toPoint:to];        
     }
 }

The second loop in the above code does a similar run through all the columns in the table, calculating the start and end points of each line and drawing the line to the PDF.

If you run the application again, you will see that our table is complete!

But what use is a table without data?

Populating the Table

We're going to manually add some dummy data to our table using a series of arrays. But you could easily modify this to feed in data inputed by the user.

Add the following method called drawTableDataAt to PDFRenderer.m (right above drawPDF):

+(void)drawTableDataAt:(CGPoint)origin 
    withRowHeight:(int)rowHeight 
   andColumnWidth:(int)columnWidth 
      andRowCount:(int)numberOfRows 
   andColumnCount:(int)numberOfColumns
 {  
    NSArray* headers = [NSArray arrayWithObjects:@"Quantity", @"Description", @"Unit price", @"Total", nil];
    NSArray* invoiceInfo1 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    NSArray* invoiceInfo2 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    NSArray* invoiceInfo3 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    NSArray* invoiceInfo4 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    
    NSArray* allInfo = [NSArray arrayWithObjects:headers, invoiceInfo1, invoiceInfo2, invoiceInfo3, invoiceInfo4, nil];
    
    for(int i = 0; i < [allInfo count]; i++)
    {
        NSArray* infoToDraw = [allInfo objectAtIndex:i];
        
        for (int j = 0; j < numberOfColumns; j++) 
        {
            
            int newOriginX = origin.x + (j*columnWidth);
            int newOriginY = origin.y + ((i+1)*rowHeight);
            
            CGRect frame = CGRectMake(newOriginX, newOriginY, columnWidth, rowHeight);
                        
            [self drawText:[infoToDraw objectAtIndex:j] inFrame:frame];
        }        
    }    
 }

This code block begins by creating the data that will be in the table. The first array contains the values that will be in the header (first row of the table). The next three arrays contain the values for each row and column of the table.

The final array is a master array containing all the others, effectively making it a two-dimensional array modeling our table data.

After that, there are two nested loops. The outer loop runs through each row and extracts the data for the row.

The inner loop runs through each column and calculates the starting point of the text, depending on its location in the table. It creates a frame for the text and then draws the text in the frame.

Add a call to this new method in the drawPDF method (right before the call to UIGraphicsEndPDFContext):

[self drawTableDataAt:CGPointMake(xOrigin, yOrigin) withRowHeight:rowHeight andColumnWidth:columnWidth andRowCount:numberOfRows andColumnCount:numberOfColumns];

Run the application in the simulator and you will see our table is populated with data.

Looks good, doesn't it? But let's make one final tweak: we could use some padding between the table lines and the data itself.

Below is the final state of the drawTableAt method:

+(void)drawTableDataAt:(CGPoint)origin 
    withRowHeight:(int)rowHeight 
   andColumnWidth:(int)columnWidth 
      andRowCount:(int)numberOfRows 
   andColumnCount:(int)numberOfColumns
{
    int padding = 10; 
    
    NSArray* headers = [NSArray arrayWithObjects:@"Quantity", @"Description", @"Unit price", @"Total", nil];
    NSArray* invoiceInfo1 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    NSArray* invoiceInfo2 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    NSArray* invoiceInfo3 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    NSArray* invoiceInfo4 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
    
    NSArray* allInfo = [NSArray arrayWithObjects:headers, invoiceInfo1, invoiceInfo2, invoiceInfo3, invoiceInfo4, nil];
    
    for(int i = 0; i < [allInfo count]; i++)
    {
        NSArray* infoToDraw = [allInfo objectAtIndex:i];
        
        for (int j = 0; j < numberOfColumns; j++) 
        {            
            int newOriginX = origin.x + (j*columnWidth);
            int newOriginY = origin.y + ((i+1)*rowHeight);
            
            CGRect frame = CGRectMake(newOriginX + padding, newOriginY + padding, columnWidth, rowHeight);
                        
            [self drawText:[infoToDraw objectAtIndex:j] inFrame:frame];
        }        
    }    
}

Now we can sit back and admire the final invoice PDF, displaying image, table and data. Our work is done... that is, until it's time for all of us to start billing for RayWenderlich.com. :P

Tope Abayomi

Contributors

Tope Abayomi

Author

Over 300 content creators. Join our team.