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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4362734/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:14:11  来源:igfitidea点击:

iOS SDK - Programmatically generate a PDF file

iphoneobjective-cipadiospdf-generation

提问by WrightsCS

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

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

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

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

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

我很想知道是否有关于 iOS SDK 的任何好的 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.

我看过这个教程,PDF 创建教程,但它主要是用 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);
}

EDIT:Here's an example on GitHub using libHaruin an iPhone project.

编辑:这是在 iPhone 项目中使用 libHaru 的 GitHub 上的示例。

采纳答案by cbranch

A couple things...

几件事...

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:

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

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

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:

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

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

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

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.

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

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.

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

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

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

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).

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

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";

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

然后我调用一个函数将视图渲染到 PDF 文件。此函数递归地遍历视图层次结构并呈现每个子视图。对于我的项目,我只需要支持 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];
        }
    }
}

As an example, here's my implementation of addImageView (HPDF_ functions are from libHaru):

作为一个例子,这是我实现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);
        }
    }
}

Hopefully that gives you the idea.

希望这能给你这个想法。

回答by Bani Uppal

It's a late reply, but as i struggled a lot with pdf generation, i considered it worthwhile to share my views. Instead of Core graphics, to create a context, you can also use UIKit methods to generate a pdf.

这是一个迟到的回复,但由于我在 pdf 生成方面挣扎了很多,我认为分享我的观点是值得的。除了核心图形,要创建上下文,您还可以使用 UIKit 方法生成 pdf。

Apple has documented it well in the drawing and printing guide.

Apple 在绘图和打印指南中很好地记录了它

回答by Ben Zotto

The PDF functions on iOS are all CoreGraphics based, which makes sense, because the draw primitives in iOS are also CoreGraphics based. If you want to be rendering 2D primitives directly into a PDF, you'll have to use CoreGraphics.

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

There are some shortcuts for objects that live in UIKit as well, like images. Drawing a PNG to a PDF context still requires the call to CGContextDrawImage, but you can do it with the CGImage that you can get from a UIImage, e.g.:

UIKit 中的对象也有一些快捷方式,比如图像。将 PNG 绘制到 PDF 上下文仍然需要调用CGContextDrawImage,但您可以使用可以从 UIImage 获取的 CGImage 来完成,例如:

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

If you want more guidance on overall design, please be more explicit about what you mean by "various objects throughout your app" and what you're trying to accomplish.

如果您需要有关整体设计的更多指导,请更明确地说明“整个应用程序中的各种对象”的含义以及您要实现的目标。

回答by Paul Jowett

Late, but possibly helpful to others. It sounds like a PDF template style of operation might be the approach you want rather than building the PDF in code. Since you want to email it off anyway, you are connected to the net so you could use something like the Docmosiscloud service which is effectively a mail-merge service. Send it the data/images to merge with your template. That type of approach has the benefit of a lot less code and offloading most of the processing from your iPad app. I've seen it used in an iPad app and it was nice.

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