2010-12-06 85 views
76

使用CoreGraphics框架是枯燥的工作,在我诚实的看来,当涉及到以编程方式绘制PDF文件。iOS SDK - 以编程方式生成一个PDF文件

我想以编程方式创建一个PDF,在我的应用程序中使用视图中的各种对象。

我很想知道是否有任何有关iOS SDK的优秀PDF教程,也许是库中的一个下拉菜单。

我见过这个教程,PDF Creation Tutorial,但它主要是用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项目上GitHub using libHaru一个例子。

+1

哈哈已记录得很好,我知道这是旧的,但在iPhone上100页的PDF是什么。它会比iOS设备更强调服务器,因为服务器将不得不乘以尝试这样做的并发用户的数量。 – Armand 2015-09-26 21:10:57

回答

56

一对夫妇的事情...

首先,是与CoreGraphics中生成PDF在iOS设备导致损坏的PDF文件中的错误。我知道这个问题存在和包括iOS 4.1(我还没有测试iOS 4.2)。该问题与字体有关,只有在您的PDF中包含文字时才会显示。该症状是,生成PDF文件时,你会看到在调试控制台看起来像这样的错误:

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

棘手的方面是,生成的PDF将使一些的PDF阅读器不错,但不能在其他地方渲染。因此,如果您可以控制将用于打开PDF的软件,则可以忽略此问题(例如,如果您只打算在iPhone或Mac桌面上显示PDF,那么您应该很好地使用CoreGraphics中)。但是,如果您需要创建可在任何地方使用的PDF,那么您应该仔细看看这个问题。下面是一些额外的信息:

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

作为一种变通方法,我用libHaru成功地在iPhone上为CoreGraphics中生成PDF的替代品。让libHaru最初与我的项目一起构建起来有点棘手,但是一旦我的项目设置正确,它就可以很好地满足我的需求。其次,根据PDF的格式/布局,您可能会考虑使用Interface Builder创建一个视图,作为PDF输出的“模板”。然后,您将编写代码来加载视图,填写任何数据(例如,为UILabels设置文本等),然后将视图的各个元素渲染到PDF中。换句话说,使用IB来指定坐标,字体,图像等,并编写代码以通用的方式渲染各种元素(例如,UILabel,UIImageView等),因此您不必硬编码所有内容。我使用了这种方法,它对我的​​需求很有帮助。同样,这可能会或可能不会对您的情况有意义,具体取决于PDF的格式/布局需求。

编辑:(针对第一个评论)

我的实现是一个商业产品意味着我不能共享的完整代码的一部分,但我可以给一个大致轮廓:

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

在代码中,我加载视图:

- (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文件。此函数递归地遍历视图层次结构并呈现每个子视图。对于我的项目,我需要支持只有标签,ImageView的,和视图(嵌套视图):

- (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); 
     } 
    } 
} 

希望这给你的想法。

+0

您是否有任何关于如何将XIB用作模板的示例? – WrightsCS 2010-12-06 05:14:46

8

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

还有一些生活在UIKit中的对象的快捷方式,例如图像。绘制PNG为PDF方面仍然需要调用CGContextDrawImage,但你可以用CGImage,你可以从一个UIImage得到的,如做:

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

如果您想对整体设计更多的指导,请更清楚地说明你的意思是“整个应用中的各种对象”以及你想要完成的事情。

+0

感谢您的回复。通过对象,我只是指我正在接收用户输入的文本,并希望将它们放置在PDF上的各个位置。正如你所指出的,一些图形。就像可以通过电子邮件发送的表单一样。 – WrightsCS 2010-12-06 03:21:58

+0

嗨,我有一个问题:通过这种方式(CGContextDrawImage),pdf不再是矢量。如果我想将多个矢量图形渲染到pdf的单页中,我该怎么做,同时确保pdf也是矢量 – Paradise 2015-12-25 07:59:29

2

晚了,但可能对他人有帮助。这听起来像一个PDF模板的操作风格可能是你想要的方法,而不是在代码中构建PDF。因为无论如何您都要通过电子邮件发送邮件,因此您已连接到网络,因此您可以使用类似Docmosis云服务的邮件合并服务。将数据/图像发送给您的模板进行合并。这种方法的好处是代码少很多,并且可以从iPad应用程序中卸载大部分处理。我已经看到它在iPad应用程序中使用,它很好。

12

这是一个很晚的答复,但由于我与pdf一代挣扎了很多,我认为值得分享我的观点。为了创建上下文,您可以使用UIKit方法来生成pdf,而不是Core graphics。

苹果在drawing and printing guide.