79 votes

iOS SDK - générer par programme un fichier PDF

À l'aide de la CoreGraphics cadre est un travail pénible, à mon avis honnête, quand il s'agit de programmation pour le dessin d'un fichier PDF.

Je voudrais créer par programmation un fichier PDF, à l'aide de divers objets à partir de points de vue tout au long de mon application.

Je suis intéressé de savoir s'il y a des bonnes PDF tutoriels pour le SDK iOS, peut-être une goutte dans la bibliothèque.

J'ai vu ce tutoriel, la Création de PDF Tutoriel, mais il a principalement été écrit en C. la Recherche pour plus d'Objective-C style. Elle apparaît comme une sorte ridicule d'écrire dans un fichier PDF, d'avoir à calculer où les lignes et les autres objets seront placés.

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: Voici un exemple sur GitHub à l'aide de libHaru dans un iPhone de projet.

56voto

cbranch Points 2656

Un couple de choses...

Tout d'abord, il y a un bug avec CoreGraphics la génération de PDF dans iOS qui entraîne une altération des fichiers Pdf. Je sais que ce problème existe jusqu'à et y compris iOS 4.1 (je n'ai pas testé iOS 4.2). Le problème est lié aux polices et ne s'affiche que si vous incluez le texte de votre PDF. Le problème est que, lorsque le PDF généré, vous verrez des erreurs dans la console de débogage qui ressemblent à ceci:

<Error>: can't get CIDs for glyphs for 'TimesNewRomanPSMT'

Le difficile, c'est que le fichier PDF résultant rendra bien dans certains lecteurs de PDF, mais ne parviennent pas à rendre dans d'autres lieux. Donc, si vous avez le contrôle sur le logiciel qui sera utilisé pour ouvrir le fichier PDF, vous pouvez être en mesure d'ignorer ce problème (par exemple, si vous avez l'intention d'afficher le fichier PDF sur le iPhone ou le Mac de bureau, alors vous devriez être bien à l'aide de CoreGraphics). Toutefois, si vous avez besoin de créer un fichier PDF qui fonctionne n'importe où, alors vous devriez jeter un oeil de plus près à cette question. Voici quelques informations supplémentaires:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/15505-pdf-font-problem-cant-get-cids-glyphs.html#post97854

Comme solution, j'ai utilisé libHaru avec succès sur l'iPhone comme un remplacement pour CoreGraphics la génération de PDF. C'était un peu difficile d'obtenir libHaru de construire avec mon projet au départ, mais une fois que j'ai obtenu mon projet d'installation correctement, il a bien fonctionné pour mes besoins.

Deuxièmement, selon le format/mise en page de votre PDF, vous pouvez envisager d'utiliser Interface Builder pour créer une idée qui sert de "modèle" pour le fichier PDF de sortie. Vous devez ensuite écrire le code pour charger la vue, de remplir toutes les données (par exemple, texte pour UILabels, etc.), puis restituer les éléments individuels de la vue dans le fichier PDF. En d'autres termes, l'utilisation de l'IB afin de spécifier les coordonnées, les polices, les images, etc. et écrire du code pour rendre les différents éléments (par exemple, UILabel, UIImageView, etc.) de façon générale, de sorte que vous n'avez pas à coder en dur tout. J'ai utilisé cette approche et cela a fonctionné très bien pour mes besoins. Encore une fois, cela peut ou ne peut faire sens pour votre situation en fonction de la mise en forme/mise en page des besoins de votre PDF.

EDIT: (réponse au 1er commentaire)

Mon application est une partie d'un produit commercial, ce qui signifie que je ne peux pas partager le code complet, mais je peux donner un aperçu général:

J'ai créé un .xib fichier avec une vue et de la taille de la vue à 850 x 1100 (mon PDF cible de 8,5 x 11 pouces, donc cela rend plus facile pour traduire vers/depuis le moment de la conception des coordonnées).

Dans le code, je charge le point de vue:

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

J'ai ensuite remplir divers éléments. J'ai utilisé les balises à trouver les éléments appropriés, mais vous pouvez également effectuer d'autres moyens. Exemple:

UILabel *label = (UILabel *)[templateView viewWithTag:TAG_FIRST_NAME];
if (label != nil) {
    label.text = (firstName != nil) ? firstName : @"None";

Ensuite, j'appelle une fonction pour rendre l'affichage du fichier PDF. Cette fonction de manière récursive les promenades le point de vue de la hiérarchie et rend chaque sous-vue. Pour mon projet, j'ai besoin d'Étiquette, ImageView, et la Vue (pour les vues imbriquées):

- (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];
        }
    }
}

Comme exemple, voici ma mise en œuvre de addImageView (HPDF_ fonctions sont de 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);
        }
    }
}

J'espère que ça vous donne l'idée.

12voto

Bani Uppal Points 759

C'est une réponse tardive, mais comme j'ai eu beaucoup de mal avec la génération de pdf, j'ai considéré qu'il vaut la peine de partager mon point de vue. Au lieu de graphiques de Base, de créer un contexte, vous pouvez également utiliser UIKit méthodes pour générer un pdf.

Apple a documenté dans le dessin et l'impression du guide.

Aussi, il y a un bon tutoriel sur la génération d'un pdf: check it out.

8voto

Ben Zotto Points 32105

Le fichier PDF fonctionne sur iOS sont tous CoreGraphics, ce qui est logique, car le tirage au sort des primitives dans iOS sont également CoreGraphics base. Si vous voulez être rendu 2D primitives directement dans un fichier PDF, vous devez utiliser CoreGraphics.

Il y a quelques raccourcis pour les objets qui vivent dans UIKit ainsi, comme des images. Dessin PNG PDF contexte nécessite encore à l'appel d' CGContextDrawImage, mais vous pouvez le faire avec le CGImage que vous pouvez obtenir à partir d'une UIImage, par exemple:

UIImage * myPNG = [UIImage imageNamed:@"mypng.png"];
CGContextDrawImage (pdfContext, CGRectMake(200, 200, 207, 385), [myPNG CGImage]);

Si vous voulez plus de conseils sur la conception d'ensemble, s'il vous plaît être plus explicite sur ce que tu veux dire par "divers objets tout au long de votre app" et de ce que vous essayez d'accomplir.

2voto

jowierun Points 4127

En retard, mais peut-être utile à d'autres personnes. Il sonne comme un PDF de style de modèle de fonctionnement pourrait être l'approche que vous voulez, plutôt que de mettre le PDF dans le code. Puisque vous voulez l'envoyer par courriel de toute façon, vous êtes connecté sur le net afin que vous pouvez utiliser quelque chose comme le Docmosis service de cloud qui est en fait une fusion de services. Envoyer des données et des images à fusionner avec votre modèle. Ce type d'approche a l'avantage de beaucoup moins de code et de déchargement de la majeure partie du traitement à partir de votre iPad. Je l'ai vu utilisé dans une application pour iPad et c'était agréable.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X