b2cloud

12th July 2011

PDF Rendering on iOS

Tutorial By 5 years ago

There are a couple of ways in which you can render a PDF on an iOS Device, and it really depends on what you’re trying to do as to which way you should take.
If you are simply trying to render a PDF to a user then the best way would be to make a UIWebView and load the PDF in as a URL, this will display a PDF in the way Mobile Safari does, allowing the user to zoom, scroll and navigate between pages.
If however, you want a little more control over the way a PDF is displayed then you are stuck with the CGPDFDocument API (I am going to discount 3rd party API’s because I haven’t researched them properly). It’s important to understand that the CG at the start of CGPDFDocument is an abbreviation of “Core Graphics”, being a graphically library the only output it’s going to give you is an image or a render onto a context, so unfortunately zooming, page turning and all those other goodies are going to have to be coded by yourself. So let’s get started, the first thing you need to render a PDF is a reference to where the PDF is stored, in my case I just dropped one into the bundle, so to open it the following code will be used:

NSString* pdfFullPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"pdf"];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)[NSURL fileURLWithPath:pdfFullPath]);

Notice the typecasting of an NSURL to a CFURLRef, a lot of people don’t realise that it is in fact as simple as this. Now the next thing you will want to do is get the number of pages in the pdf, this will set the limits to how far your user can scroll, to get this only 1 simple call is needed:

NSInteger numberOfPages = CGPDFDocumentGetNumberOfPages(pdf);

Now that we have the number of pages as a limit, we will want to render a page in a graphical context, this is where things get a bit tricky, because Core Graphics uses a slightly different coordinate system to UIView’s, without any graphical transformation the image will be rendered horizontally and vertically flipped, and not to the aspect scale that is apparent in the actual PDF. The following function performs these corrections:

+(UIImage*) imageFromPDF:(CGPDFDocumentRef)pdf withPageNumber:(NSUInteger)pageNumber withScale:(CGFloat)scale
{
	if(pageNumber > 0 && pageNumber < CGPDFDocumentGetNumberOfPages(pdf))
	{
		CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdf,pageNumber);
		CGRect tmpRect = CGPDFPageGetBoxRect(pdfPage,kCGPDFMediaBox);
		CGRect rect = CGRectMake(tmpRect.origin.x,tmpRect.origin.y,tmpRect.size.width*scale,tmpRect.size.height*scale);
		UIGraphicsBeginImageContext(rect.size);
		CGContextRef context = UIGraphicsGetCurrentContext();
		CGContextTranslateCTM(context,0,rect.size.height);
		CGContextScaleCTM(context,scale,-scale);
		CGContextDrawPDFPage(context,pdfPage);
		UIImage* pdfImage = UIGraphicsGetImageFromCurrentImageContext();
		UIGraphicsEndImageContext();
		return pdfImage;
	}
	return nil;
}

This essentially converts a page of a PDF into a UIImage, allowing you to display it in a UIImageView on screen. If you use a scale factor of 1, you may notice that it is slightly blurred. I found that the CGPDFPageGetBoxRect function tends to output quite a small rectangle with the right aspect ratio, this means it has to be scaled up manually (by using the scale factor) and drawn into this bigger view. After you have this UIImageView, if you want to build a fully fledged PDF viewer you should combine UIImageView’s into a paged UIScrollView, with zooming built in.
But wait there is still some clean up to do when we leave the PDF viewer, we must release the document like so:

CGPDFDocumentRelease(pdf);

It’s worth pointing out that this API is only used for PDF displaying, PDF creation, highlighting or any other kind of interaction needs some extra smarts behind it that this tutorial will not go into, maybe later.

  • Thanks for this. One small note… the line if(pageNumber > 0 && pageNumber 0 && pageNumber <= CGPDFDocumentGetNumberOfPages(pdf)) to allow the last page in the doc to be selected.

Recommended Posts

iOS performSelector with multiple parameters

Post by 5 years ago

On iOS the built in convenience method performSelector method call only allows for up to 2 parameters. - (void) aMethod { [self performSelector:@selector(doSomethingWithObject:otherObject:) withObject:@(1) withObject:@(2)]; } - (void) doSomethingWithObject:(id) object otherObject:(id) otherObject { // Code

Got an idea?

We help entrepreneurs, organizations and established brands from around
the country bring ideas to life. We would love to hear from you!