iOS SDK - программное создание файла PDF


79

По моему честному мнению, использование фреймворка 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);
}

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


1
ха-ха, я знаю, что это старый, но 100-страничный pdf на iPhone - ничто. это будет нагружать сервер больше, чем устройство iOS, так как серверу придется умножить это на количество одновременных пользователей, пытающихся это сделать.
Armand

Ответы:


56

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

Во-первых, есть ошибка с генерацией 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;
}

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

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

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


У вас есть примеры использования XIB в качестве шаблона?
WrightsCS

12

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

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


4
FWIW - сайт учебника тупиковый для Godaddy.
CuriousRabbit 06

8

Все функции 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-файла. И, как вы отметили, немного графики. Что-то вроде формы, которую можно отправить по электронной почте.
WrightsCS 06

Привет, парень, у меня вопрос: таким образом (CGContextDrawImage) PDF-файл больше не является векторным. Что мне делать, если я хочу визуализировать несколько векторных изображений на одной странице PDF-файла, а пока убедитесь, что PDF-файл также является векторным
Paradise

2

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.