iOS SDK - 以编程方式生成 PDF 文件 [英] iOS SDK - Programmatically generate a PDF file

查看:40
本文介绍了iOS SDK - 以编程方式生成 PDF 文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

老实说,在以编程方式绘制 PDF 文件时,使用 CoreGraphics 框架是一项乏味的工作.

Using the CoreGraphics framework is tedious work, in my honest opinion, when it comes to programmatically drawing a PDF file.

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

I would like to programmatically create a PDF, using various objects from views throughout my app.

我很想知道是否有关于 iOS SDK 的任何好的 PDF 教程,也许是库中的一个下降.

I am interested to know if there are any good PDF tutorials around for the iOS SDK, maybe a drop in library.

我看过这个教程,PDF创建教程,但它主要是用 C 编写的.寻找更多的 Objective-C 风格.这似乎也是一种写入 PDF 文件的荒谬方式,必须计算线条和其他对象的放置位置.

I've seen this tutorial, PDF Creation Tutorial, but it was mostly written in C. Looking for more Objective-C style. This also seems like a ridiculous way to write to a PDF file, having to calculate where lines and other objects will be placed.

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

以下是使用 libHaru 的 GitHub 的示例 在 iPhone 项目中.

Here's an example on GitHub using libHaru in an iPhone project.

推荐答案

一些事情...

首先,iOS 中的 CoreGraphics PDF 生成存在一个错误,导致 PDF 损坏.我知道这个问题一直存在到并包括 iOS 4.1(我还没有测试过 iOS 4.2).该问题与字体有关,仅当您在 PDF 中包含文本时才会出现.症状是,在生成 PDF 时,您会在调试控制台中看到如下所示的错误:

First, there is a bug with CoreGraphics PDF generation in iOS that results in corrupted PDFs. I know this issue exists up to and including iOS 4.1 (I haven't tested iOS 4.2). The issue is related to fonts and only shows up if you include text in your PDF. The symptom is that, when generating the PDF, you'll see errors in the debug console that look like this:

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

棘手的方面是生成的 PDF 在某些 PDF 阅读器中可以很好地呈现,但在其他地方却无法呈现.因此,如果您可以控制用于打开 PDF 的软件,您可能可以忽略此问题(例如,如果您只想在 iPhone 或 Mac 桌面上显示 PDF,那么您应该可以使用核心图形).但是,如果您需要创建一个适用于任何地方的 PDF,那么您应该仔细研究一下这个问题.以下是一些附加信息:

The tricky aspect is that the resulting PDF will render fine in some PDF readers, but fail to render in other places. So, if you have control over the software that will be used to open your PDF, you may be able to ignore this issue (e.g., if you only intend to display the PDF on the iPhone or Mac desktops, then you should be fine using CoreGraphics). However, if you need to create a PDF that works anywhere, then you should take a closer look at this issue. Here's some additional info:

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

作为一种解决方法,我在 iPhone 上成功地使用了 libHaru 作为 CoreGraphics PDF 生成的替代品.最初让 libHaru 与我的项目一起构建有点棘手,但是一旦我正确设置了我的项目,它就可以很好地满足我的需求.

As a workaround, I've used libHaru successfully on iPhone as a replacement for CoreGraphics PDF generation. It was a little tricky getting libHaru to build with my project initially, but once I got my project setup properly, it worked fine for my needs.

其次,根据 PDF 的格式/布局,您可以考虑使用 Interface Builder 创建一个视图,作为 PDF 输出的模板".然后,您将编写代码来加载视图,填充任何数据(例如,为 UILabels 设置文本等),然后将视图的各个元素呈现到 PDF 中.换句话说,用IB指定坐标、字体、图片等,编写代码在泛型中渲染各种元素(如UILabelUIImageView等)这样您就不必对所有内容进行硬编码.我使用了这种方法,它非常适合我的需求.同样,这可能对您的情况有意义,也可能没有意义,具体取决于您的 PDF 的格式/布局需求.

Second, depending on the format/layout of your PDF, you might consider using Interface Builder to create a view that serves as a "template" for your PDF output. You would then write code to load the view, fill in any data (e.g., set text for UILabels, etc.), then render the individual elements of the view into the PDF. In other words, use IB to specify coordinates, fonts, images, etc. and write code to render various elements (e.g., UILabel, UIImageView, etc.) in a generic way so you don't have to hard-code everything. I used this approach and it worked out great for my needs. Again, this may or may not make sense for your situation depending on the formatting/layout needs of your PDF.

(对第一条评论的回应)

(response to 1st comment)

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

My implementation is part of a commercial product meaning that I can't share the full code, but I can give a general outline:

我创建了一个带有视图的 .xib 文件,并将视图的大小调整为 850 x 1100(我的 PDF 的目标是 8.5 x 11 英寸,因此可以轻松地与设计时坐标进行转换.

I created a .xib file with a view and sized the view to 850 x 1100 (my PDF was targeting 8.5 x 11 inches, so this makes it easy to translate to/from design-time coordinates).

在代码中,我加载视图:

In code, I load the view:

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

然后我填写各种元素.我使用标签来找到合适的元素,但你可以通过其他方式来做到这一点.示例:

I then fill in various elements. I used tags to find the appropriate elements, but you could do this other ways. Example:

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

然后我调用一个函数将视图渲染到 PDF 文件.此函数递归地遍历视图层次结构并呈现每个子视图.对于我的项目,我只需要支持 Label、ImageView 和 View(对于嵌套视图):

Then I call a function to render the view to the PDF file. This function recursively walks the view hierarchy and renders each subview. For my project, I need to support only Label, ImageView, and View (for nested views):

- (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):

As an example, here's my implementation of addImageView (HPDF_ functions are from 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);
        }
    }
}

希望这能给你灵感.

这篇关于iOS SDK - 以编程方式生成 PDF 文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆