iOS SDK - программно сгенерировать файл PDF
Использование платформы CoreGraphics, по моему честному мнению, является утомительной работой, когда речь идет о программном рисовании файла PDF.
Я хотел бы программно создать PDF, используя различные объекты из представлений по всему моему приложению.
Мне интересно знать, есть ли какие-нибудь хорошие учебники по PDF для iOS SDK, возможно, добавление библиотеки.
Я видел этот учебник, 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);
}
РЕДАКТИРОВАТЬ: Вот пример на GitHub с использованием libHaru в проекте iPhone.
4 ответа
Пара вещей...
Во-первых, в CoreGraphics PDF генерируется ошибка в iOS, которая приводит к повреждению PDF-файлов. Я знаю, что эта проблема существует вплоть до iOS 4.1 (включая iOS 4.2). Эта проблема связана со шрифтами и появляется только в том случае, если вы включили текст в свой PDF. Симптом заключается в том, что при создании PDF вы увидите ошибки в консоли отладки, которые выглядят так:
<Error>: can't get CIDs for glyphs for 'TimesNewRomanPSMT'
Сложный аспект заключается в том, что полученный PDF-файл будет хорошо отображаться в некоторых программах чтения PDF-файлов, но не будет отображаться в других местах. Таким образом, если у вас есть контроль над программным обеспечением, которое будет использоваться для открытия вашего PDF-файла, вы можете игнорировать эту проблему (например, если вы намереваетесь отображать PDF-файл только на настольных компьютерах iPhone или Mac, тогда у вас должно получиться CoreGraphics). Однако, если вам нужно создать PDF-файл, который работает где угодно, вам следует более внимательно изучить эту проблему. Вот дополнительная информация:
В качестве обходного пути я успешно использовал libHaru на iPhone в качестве замены CoreGraphics PDF для генерации. Поначалу было немного сложно заставить libHaru строить с моим проектом, но как только я правильно настроил свой проект, он отлично работал для моих нужд.
Во-вторых, в зависимости от формата / макета вашего PDF, вы можете рассмотреть возможность использования Interface Builder для создания представления, которое служит "шаблоном" для вывода PDF. Затем вы должны написать код для загрузки представления, заполнить любые данные (например, задать текст для UILabels и т. Д.), А затем отобразить отдельные элементы представления в PDF. Другими словами, используйте IB, чтобы указать координаты, шрифты, изображения и т. Д. И написать код для визуализации различных элементов (например, UILabel
, UIImageView
и т. д.), чтобы вам не приходилось все кодировать жестко. Я использовал этот подход, и он отлично подошел для моих нужд. Опять же, это может иметь или не иметь смысла для вашей ситуации в зависимости от потребностей форматирования / макета вашего PDF.
РЕДАКТИРОВАТЬ: (ответ на 1-й комментарий)
Моя реализация является частью коммерческого продукта, и это означает, что я не могу поделиться полным кодом, но могу дать общую схему:
Я создал файл.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-файл. Эта функция рекурсивно обходит иерархию представления и отображает каждое подпредставление. Для моего проекта мне нужно поддерживать только 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, я счел целесообразным поделиться своими взглядами. Вместо базовой графики для создания контекста вы также можете использовать методы UIKit для создания PDF.
Apple хорошо задокументировала это в руководстве по рисованию и печати.
Все функции PDF на iOS основаны на 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]);
Если вы хотите получить больше рекомендаций по общему дизайну, пожалуйста, будьте более точны в том, что вы подразумеваете под "различными объектами в вашем приложении" и что вы пытаетесь достичь.
Поздно, но, возможно, полезно для других. Похоже, стиль работы шаблона PDF может быть подходом, который вам нужен, а не созданием PDF в коде. Так как вы все равно хотите отправить его по электронной почте, вы подключены к сети, чтобы вы могли использовать что-то вроде облачной службы Docmosis, которая фактически является службой слияния. Отправьте ему данные / изображения, чтобы объединить их с вашим шаблоном. Преимущество такого подхода заключается в том, что кода гораздо меньше, а большая часть обработки загружается из приложения для iPad. Я видел его в приложении для iPad, и это было приятно.