iOS SDK – 以编程方式生成PDF文件

在我看来,使用CoreGraphics框架是一件繁琐的工作,当涉及到以编程方式绘制PDF文件时。

我想以编程方式创buildPDF ,使用视图中的各种对象贯穿我的应用程序。

我有兴趣知道是否有任何有关iOS SDK的PDF教程,也许是一个库的下降。

我已经看过这个教程, PDF创build教程 ,但它主要是用C编写的。寻找更多的Objective-C风格。 这也似乎是写一个PDF文件的荒谬的方式,不得不计算线和其他对象将放置在哪里。

void CreatePDFFile (CGRect pageRect, const char *filename) { // This code block sets up our PDF Context so that we can draw to it CGContextRef pdfContext; CFStringRef path; CFURLRef url; CFMutableDictionaryRef myDictionary = NULL; // Create a CFString from the filename we provide to this method when we call it path = CFStringCreateWithCString (NULL, filename, kCFStringEncodingUTF8); // Create a CFURL using the CFString we just defined url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, 0); CFRelease (path); // This dictionary contains extra options mostly for 'signing' the PDF myDictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(myDictionary, kCGPDFContextTitle, CFSTR("My PDF File")); CFDictionarySetValue(myDictionary, kCGPDFContextCreator, CFSTR("My Name")); // Create our PDF Context with the CFURL, the CGRect we provide, and the above defined dictionary pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary); // Cleanup our mess CFRelease(myDictionary); CFRelease(url); // Done creating our PDF Context, now it's time to draw to it // Starts our first page CGContextBeginPage (pdfContext, &pageRect); // Draws a black rectangle around the page inset by 50 on all sides CGContextStrokeRect(pdfContext, CGRectMake(50, 50, pageRect.size.width - 100, pageRect.size.height - 100)); // This code block will create an image that we then draw to the page const char *picture = "Picture"; CGImageRef image; CGDataProviderRef provider; CFStringRef picturePath; CFURLRef pictureURL; picturePath = CFStringCreateWithCString (NULL, picture, kCFStringEncodingUTF8); pictureURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), picturePath, CFSTR("png"), NULL); CFRelease(picturePath); provider = CGDataProviderCreateWithURL (pictureURL); CFRelease (pictureURL); image = CGImageCreateWithPNGDataProvider (provider, NULL, true, kCGRenderingIntentDefault); CGDataProviderRelease (provider); CGContextDrawImage (pdfContext, CGRectMake(200, 200, 207, 385),image); CGImageRelease (image); // End image code // Adding some text on top of the image we just added CGContextSelectFont (pdfContext, "Helvetica", 16, kCGEncodingMacRoman); CGContextSetTextDrawingMode (pdfContext, kCGTextFill); CGContextSetRGBFillColor (pdfContext, 0, 0, 0, 1); const char *text = "Hello World!"; CGContextShowTextAtPoint (pdfContext, 260, 390, text, strlen(text)); // End text // We are done drawing to this page, let's end it // We could add as many pages as we wanted using CGContextBeginPage/CGContextEndPage CGContextEndPage (pdfContext); // We are done with our context now, so we release it CGContextRelease (pdfContext); } 

编辑:这是一个在iPhone项目中使用libHaru的GitHub的例子。

几件事情…

首先,iOS中的CoreGraphics PDF生成有一个错误,导致PDF损坏。 我知道这个问题存在,并包括iOS 4.1(我还没有testingiOS 4.2)。 该问题与字体有关,只有在您的PDF中包含文字时才会显示。 症状是,在生成PDF时,您将在debugging控制台中看到如下所示的错误:

 <Error>: can't get CIDs for glyphs for 'TimesNewRomanPSMT' 

棘手的方面是,所产生的PDF将在一些PDF阅读器中呈现罚款,但无法在其他地方呈现。 所以,如果你可以控制将用于打开PDF的软件,那么你可以忽略这个问题(例如,如果你只打算在iPhone或Mac桌面上显示PDF,那么你应该很好地使用CoreGraphics中)。 但是,如果你需要创build一个可以在任何地方工作的PDF,那么你应该仔细看看这个问题。 这里有一些额外的信息:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/15505-pdf-font-problem-cant-get-cids-glyphs.html#post97854

作为一种解决方法,我已经成功地在iPhone上使用libHaru作为CoreGraphics PDF生成的替代品。 最初让我的项目build立libHaru是一个棘手的问题,但是一旦我的项目设置正确,它就能正常工作,满足我的需求。

其次,根据PDF的格式/布局,您可能会考虑使用Interface Builder创build一个视图,作为PDF输出的“模板”。 然后,您将编写代码来加载视图,填写任何数据(例如,为UILabels设置文本等),然后将视图的各个元素渲染到PDF中。 换句话说,使用IB来指定坐标,字体,图像等,并编写代码以通用的方式呈现各种元素(例如, UILabelUIImageView等),因此您不必硬编码所有内容。 我使用了这种方法,它对我的​​需求很有帮助。 再一次,这可能会或可能不会对您的情况有意义取决于您的PDF格式/布局的需要。

编辑:(对第一评论的回应)

我的实现是商业产品的一部分,这意味着我不能分享完整的代码,但我可以给出一个总体概述:

我创build了一个带有视图的.xib文件,并将视图大小设置为850 x 1100(我的PDF定位为8.5 x 11英寸,因此可以很容易地翻译到devise时坐标)。

在代码中,我加载视图:

 - (UIView *)loadTemplate { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ReportTemplate" owner:self options:nil]; for (id view in nib) { if ([view isKindOfClass: [UIView class]]) { return view; } } return nil; } 

然后我填写各种元素。 我使用标签来查找适当的元素,但是您可以通过其他方式来实现。 例:

 UILabel *label = (UILabel *)[templateView viewWithTag:TAG_FIRST_NAME]; if (label != nil) { label.text = (firstName != nil) ? firstName : @"None"; 

然后我调用一个函数来将视图呈现给PDF文件。 这个函数recursion地遍历视图层次结构并呈现每个子视图。 对于我的项目,我只需要支持Label,ImageView和View(对于嵌套视图):

 - (void)addObject:(UIView *)view { if (view != nil && !view.hidden) { if ([view isKindOfClass:[UILabel class]]) { [self addLabel:(UILabel *)view]; } else if ([view isKindOfClass:[UIImageView class]]) { [self addImageView:(UIImageView *)view]; } else if ([view isKindOfClass:[UIView class]]) { [self addContainer:view]; } } } 

作为一个例子,这里是我的addImageView的实现(HPDF_函数来自libHaru):

 - (void)addImageView:(UIImageView *)imageView { NSData *pngData = UIImagePNGRepresentation(imageView.image); if (pngData != nil) { HPDF_Image image = HPDF_LoadPngImageFromMem(_pdf, [pngData bytes], [pngData length]); if (image != NULL) { CGRect destRect = [self rectToPDF:imageView.frame]; float x = destRect.origin.x; float y = destRect.origin.y - destRect.size.height; float width = destRect.size.width; float height = destRect.size.height; HPDF_Page page = HPDF_GetCurrentPage(_pdf); HPDF_Page_DrawImage(page, image, x, y, width, height); } } } 

希望这给你的想法。

这是一个很晚的答复,但是由于我在pdf方面挣扎了很多,我认为值得分享我的观点。 创build一个上下文,而不是核心graphics,你也可以使用UIKit方法来生成一个PDF。

苹果已经在绘图和打印指南中logging了它。

iOS上的PDFfunction都是基于CoreGraphics的,这很有意义,因为iOS中的绘图基元也是基于CoreGraphics的。 如果您想将2D基元直接渲染到PDF中,则必须使用CoreGraphics。

还有一些生活在UIKit中的对象的快捷方式,比如图片。 将PNG绘制到PDF上下文中仍然需要调用CGContextDrawImage ,但是您可以使用可从UIImage获取的CGImage执行此操作,例如:

 UIImage * myPNG = [UIImage imageNamed:@"mypng.png"]; CGContextDrawImage (pdfContext, CGRectMake(200, 200, 207, 385), [myPNG CGImage]); 

如果您希望获得关于整体devise的更多指导,请更清楚地说明您的“整个应用程序中的各种对象”以及您要完成的内容。

晚了,但可能对他人有帮助。 这听起来像一个PDF模板的操作风格可能是你想要的方法,而不是在代码中构buildPDF。 既然你想通过电子邮件发送邮件,你可以连接到networking上,这样你就可以使用Docmosis云服务这种有效的邮件合并服务。 发送数据/图像与您的模板合并。 这种方法的好处是代码less得多,卸载了iPad应用程序的大部分处理。 我已经看到它在iPad应用程序中使用,这是很好的。