iOS SDK - программно создать файл PDF - PullRequest
79 голосов
/ 06 декабря 2010

Использование CoreGraphics Framework - это кропотливая работа, по моему честному мнению, когда дело касается программного рисования PDF-файла.

Я хотел бы программно создать PDF , используя различные объекты из представлений в моем приложении.

Мне интересно узнать, есть ли какие-нибудь хорошие учебники PDF для iOS SDK, возможно, добавление библиотеки.

I 'мы видели этот учебник, Учебник по созданию 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);
}

РЕДАКТИРОВАТЬ: Вот пример на GitHub с использованием libHaru в проекте iPhone.

Ответы [ 4 ]

56 голосов
/ 06 декабря 2010

Пара вещей ...

Во-первых, во время генерации 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.

РЕДАКТИРОВАТЬ: (ответ на 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;
}

Затем я заполняю различные элементы. Я использовал теги, чтобы найти соответствующие элементы, но вы могли бы сделать это другими способами. Пример: * +1028 *

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

Надеюсь, это даст вам идею.

12 голосов
/ 20 декабря 2011

Это поздний ответ, но, поскольку я много боролся с генерацией PDF, я счел целесообразным поделиться своими взглядами.Вместо базовой графики для создания контекста вы также можете использовать методы UIKit для создания PDF.

Apple хорошо задокументировала это в руководстве по рисованию и печати. ​​

8 голосов
/ 06 декабря 2010

Все функции 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]);

Если вы хотите получить дополнительные рекомендации по общимдизайн, пожалуйста, будьте более ясны в отношении того, что вы подразумеваете под «различными объектами в вашем приложении» и что вы пытаетесь достичь.

2 голосов
/ 02 ноября 2011

Поздно, но, возможно, полезно для других. Похоже, стиль работы шаблона PDF может быть подходом, который вы хотите, а не создавать PDF в коде. Так как вы все равно хотите отправить его по электронной почте, вы подключены к сети, поэтому вы можете использовать что-то вроде облачной службы Docmosis , которая фактически является службой слияния. Отправьте ему данные / изображения, чтобы объединить их с вашим шаблоном. Преимущество такого подхода заключается в том, что кода гораздо меньше, а большая часть обработки загружается из приложения для iPad. Я видел его в приложении для iPad, и это было здорово.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...