Using the CoreGraphics framework is tedious work, in my honest opinion, when it comes to programmatically drawing a PDF file.
在我看来,使用CoreGraphics框架在以编程的方式绘制PDF文件时是一件非常繁琐的工作。
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 libHaru in an iPhone project.
编辑:这里有一个在iPhone项目中使用libHaru的GitHub的例子。
4 个解决方案
#1
56
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文件被损坏。我知道这个问题一直存在,包括ios4.1(我还没有测试ios4.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,那么你就可以使用CoreGraphics了)。但是,如果您需要创建一个可以在任何地方工作的PDF,那么您应该更仔细地研究这个问题。这里有一些额外的信息:
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以通用的方式指定坐标、字体、图像等,并编写代码以呈现各种元素(例如UILabel、UIImageView等),这样您就不必对所有内容进行硬编码。我使用了这种方法,它非常适合我的需要。同样,这可能对您的情况有意义,也可能没有意义,这取决于您的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文件。该函数递归地遍历视图层次结构,并呈现每个子视图。对于我的项目,我只需要支持标签、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.
希望你们能理解。
#2
12
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.
苹果公司在图纸和印刷指南中有很好的记录。
#3
8
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.
如果你想要对整体设计有更多的指导,请更明确地说明你所说的“贯穿整个应用程序的各种对象”以及你想要实现的目标。
#4
2
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 Docmosis cloud 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应用程序中看到过它,它很不错。
#1
56
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文件被损坏。我知道这个问题一直存在,包括ios4.1(我还没有测试ios4.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,那么你就可以使用CoreGraphics了)。但是,如果您需要创建一个可以在任何地方工作的PDF,那么您应该更仔细地研究这个问题。这里有一些额外的信息:
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以通用的方式指定坐标、字体、图像等,并编写代码以呈现各种元素(例如UILabel、UIImageView等),这样您就不必对所有内容进行硬编码。我使用了这种方法,它非常适合我的需要。同样,这可能对您的情况有意义,也可能没有意义,这取决于您的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文件。该函数递归地遍历视图层次结构,并呈现每个子视图。对于我的项目,我只需要支持标签、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.
希望你们能理解。
#2
12
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.
苹果公司在图纸和印刷指南中有很好的记录。
#3
8
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.
如果你想要对整体设计有更多的指导,请更明确地说明你所说的“贯穿整个应用程序的各种对象”以及你想要实现的目标。
#4
2
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 Docmosis cloud 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应用程序中看到过它,它很不错。