IOS SDK - программно генерировать PDF файл
Использование структуры CoreGraphics - утомительная работа, по моему честному мнению, когда дело доходит до программного рисования PDF файла.
Я хотел бы программно создать PDF, используя различные объекты из представлений в моем приложении.
Мне интересно узнать, есть ли хорошие PDF-учебники для iOS SDK, возможно, в библиотеке.
Я видел этот учебник, Учебник по созданию 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 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 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: Здесь приведен пример GitHub с использованием libHaru в проекте iPhone.
Ответы
Ответ 1
Несколько вещей...
Во-первых, есть ошибка с генерацией 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 файл, который работает в любом месте, вам следует более внимательно изучить эту проблему. Вот еще дополнительная информация:
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.
EDIT: (ответ на 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);
}
}
}
Надеюсь, это даст вам эту идею.
Ответ 2
Это поздний ответ, но поскольку я много боролся с созданием pdf, я счел целесообразным поделиться своими взглядами. Вместо основной графики, чтобы создать контекст, вы также можете использовать методы UIKit для генерации pdf.
Apple хорошо документировала его в руководстве по рисованию и печати.
Ответ 3
Функции PDF на iOS основаны на CoreGraphics, что имеет смысл, потому что примитивы draw в 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]);
Если вам нужно больше рекомендаций по общему дизайну, будьте более ясны о том, что вы подразумеваете под "различными объектами в вашем приложении" и тем, что вы пытаетесь выполнить.
Ответ 4
Поздно, но, возможно, полезно другим. Похоже, что стиль работы в формате PDF может быть подходящим для вас, а не для создания PDF-кода. Поскольку вы все равно хотите отправить его по электронной почте, вы подключены к сети, чтобы использовать что-то вроде Docmosis облачного сервиса, который эффективно mail-merge. Отправьте ему данные/изображения, чтобы объединиться с вашим шаблоном. Такой подход имеет преимущество намного меньше кода и выгружает большую часть обработки из вашего iPad-приложения. Я видел, как он использовался в iPad-приложении, и было приятно.