PDF creation in iOS

Guides | Tutorial By 6 years ago

When I was first assigned the challenge to create a dynamic PDF in an app of ours I thought it was going to be difficult. Luckily there are some great CoreGraphics methods that make PDF creation quite easy. If you have worked with CoreGraphics before then you are already half way there, you begin a PDF then use the current CGContext which you draw to just like you would any other CGContext.

NSMutableData* pdfData = [NSMutableData data];

CGRect pageFrame = CGRectMake(0, 0, 612, 792);
CGRect printableFrame = CGRectMake(45, 39, 512, 714);

UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);

for(int page = 0; page < 5; page++)
{
	UIGraphicsBeginPDFPageWithInfo(pageFrame, nil);
	CGContextRef ctx = UIGraphicsGetCurrentContext();

	//	Draw into the ctx here...
}

UIGraphicsEndPDFContext();

//	Do whatever with the pdfData...

For an A4 sheet I have found the ‘pageFrame‘ size works well. The ‘printableFrame‘ defines the actual part I draw in, leaving a nice border around the edge of the page.

Not hard at all, but creating the pages is one thing, and drawing dynamically to the pdf is another. Whenever I do dynamic drawing I use a ‘currentY‘ variable that gets reset to the top of my page in each iteration of the loop. After I draw content at y origin of ‘currentY‘ I set ‘currentY‘ to the bottom of that content. Before drawing any content I get the frame of it and check it against the maxY value of ‘printableFrame‘, if it exceeds the printable bounds then I continue; over to the next iteration of the loop where the content will be drawn at the top of the page. If your PDF creation is done in an infinite loop and you plan to break; out when all content is drawn then be careful, if one of your content blocks’ height is greater than the printableFrame‘s height then you will never break out. I use a boolean to determine whether I force content to be drawn, even if it means going over the printable bounds.

Here is an example (and the finished product) – notice everything jumps to a new page when space runs short

NSMutableData* pdfData = [NSMutableData data];

CGRect pageFrame = CGRectMake(0, 0, 612, 792);
CGRect printableFrame = CGRectMake(45, 39, 512, 714);

UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);

CGFloat currentY = 0;
BOOL forceDraw = NO;

NSMutableString* text = [NSMutableString string];

for(int i = 0; i < 70; i++)
	[text appendString:@"long "];

int textDrawCount = 0;
int textDrawTarget = 40;

BOOL done = NO;

while(!done)
{
	currentY = CGRectGetMinY(printableFrame);
	
	UIGraphicsBeginPDFPageWithInfo(pageFrame, nil);
	//CGContextRef ctx = UIGraphicsGetCurrentContext();
	
	while(YES)
	{
		UIFont* textFont = [UIFont systemFontOfSize:17];
		CGSize textSize = [text sizeWithFont:textFont constrainedToSize:CGSizeMake(printableFrame.size.width, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
		CGRect textFrame = CGRectMake(printableFrame.origin.x, currentY, textSize.width, textSize.height);
		
		//	It's not going to fit, push it to the next page (only if we're not forcing)
		if(CGRectGetMaxY(textFrame) > CGRectGetMaxY(printableFrame) && !forceDraw)
		{
			//	In case it's still to big, avoid an infinite loop
			forceDraw = YES;
			break;
		}
		
		forceDraw = NO;
		
		[text drawInRect:textFrame withFont:textFont lineBreakMode:UILineBreakModeWordWrap];
		
		currentY = CGRectGetMaxY(textFrame);	//	Move position under content
		currentY += 20;							//	Give it some space
		
		textDrawCount++;
		
		if(textDrawCount > textDrawTarget)
		{
			done = YES;
			break;
		}
	}
}

UIGraphicsEndPDFContext();