web-dev-qa-db-fra.com

iOS SDK - Génère par programme un fichier PDF

Utiliser le framework CoreGraphics est un travail fastidieux, à mon avis honnête, quand il s’agit de dessiner par programme un fichier PDF.

Je souhaite créer par programme un [~ # ~] pdf [~ # ~] à l'aide de divers objets à partir de vues de mon application.

Je suis intéressé de savoir s’il existe de bons PDF didacticiels sur le SDK iOS, peut-être une goutte dans la bibliothèque.

J'ai vu ce tutoriel, Tutoriel de création PDF , mais il était principalement écrit en C. Nous recherchons plus de style Objective-C. Cela semble aussi être un moyen ridicule d’écrire dans un fichier PDF), car il faut 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 utilisant libHaru dans un projet iPhone.

79
WrightsCS

Un couple de choses ...

Tout d’abord, il existe un bogue avec la génération CoreGraphics PDF dans iOS qui entraîne la corruption des fichiers PDF. Je sais que ce problème existe jusqu’à iOS 4.1 et y compris (je n’ai pas testé iOS 4.2). est lié aux polices et n'apparaît que si vous incluez du texte dans votre fichier PDF. Le symptôme est que, lors de la génération du fichier PDF, des erreurs apparaissant dans la console de débogage ressemblent à ceci:

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

L’aspect délicat est que le PDF) restituera bien dans certains PDF lecteurs, mais ne sera pas rendu ailleurs. Si vous avez le contrôle de la logiciel pouvant être utilisé pour ouvrir votre fichier PDF, vous pourrez peut-être ignorer ce problème (par exemple, si vous souhaitez uniquement afficher le PDF sur le bureau de votre iPhone ou de votre Mac, cela devrait vous convenir) Cependant, si vous avez besoin de créer un PDF qui fonctionne n’importe où, vous devriez vous pencher sur ce problème. Voici quelques informations supplémentaires:

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

En guise de solution de contournement, j’ai utilisé libHaru avec succès sur iPhone pour remplacer CoreGraphics PDF génération. Il était un peu délicat d’obtenir libHaru avec mon projet au départ, mais une fois que j’ai eu la configuration de mon projet correctement, cela a bien fonctionné pour mes besoins.

Deuxièmement, en fonction du format/de la présentation de votre PDF, vous pouvez envisager d’utiliser Interface Builder pour créer une vue servant de "modèle" pour votre sortie PDF. Vous écririez ensuite le code à charger. la vue, remplissez toutes les données (par exemple, définissez le texte pour les étiquettes UIL, etc.), puis convertissez les éléments individuels de la vue au format PDF, autrement dit utilisez IB pour spécifier les coordonnées, les polices de caractères, les images, etc. pour rendre divers éléments (par exemple, UILabel, UIImageView, etc.) de manière générique afin que vous n'ayez pas à tout coder en dur. J'ai utilisé cette approche et elle a très bien fonctionné. Encore une fois, cela peut avoir un sens ou non pour votre situation en fonction des besoins de formatage/mise en page de votre PDF.

MODIFIER: (réponse au 1er commentaire)

Mon implémentation fait 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 fichier .xib avec une vue et l'ai dimensionnée à 850 x 1100 (mon PDF ciblait 8,5 x 11 pouces), ce qui facilite donc la conversion vers/à partir des coordonnées de conception ).

En code, je charge la 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;
}

Je remplis ensuite divers éléments. J'ai utilisé des balises pour trouver les éléments appropriés, mais vous pouvez le faire autrement. 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 la vue au fichier PDF. Cette fonction parcourt la hiérarchie de vues de manière récursive et restitue chaque sous-vue. Pour mon projet, je ne dois prendre en charge que Label, ImageView et View. (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];
        }
    }
}

À titre d'exemple, voici mon implémentation de addImageView (les fonctions HPDF_ proviennent 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 cela vous donne l'idée.

56
cbranch

C'est une réponse tardive, mais comme j'ai beaucoup lutté avec la génération pdf, j'ai jugé utile de partager mon point de vue. Au lieu de graphiques de base, pour créer un contexte, vous pouvez également utiliser les méthodes UIKit pour générer un fichier PDF.

Apple l’a bien documenté dans le guide de dessin et d’impression.

12
Bani Uppal

Les fonctions PDF sur iOS sont toutes basées sur CoreGraphics, ce qui est logique, car les primitives de dessin dans iOS sont également basées sur CoreGraphics. Si vous souhaitez convertir des primitives 2D directement en PDF, vous utiliser CoreGraphics.

Il existe également des raccourcis pour les objets qui vivent dans UIKit, comme les images. Dessiner un fichier PNG dans un contexte PDF nécessite toujours l'appel à CGContextDrawImage, mais vous pouvez le faire avec la CGImage que vous pouvez obtenir à partir d'un UIImage, par exemple:

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

Si vous souhaitez davantage de conseils sur la conception globale, veuillez être plus explicite sur ce que vous entendez par "divers objets dans votre application" et sur ce que vous essayez d'accomplir.

8
Ben Zotto

En retard, mais peut-être utile aux autres. Cela ressemble à un style d'opération PDF pourrait être l'approche que vous souhaitez plutôt que de créer le PDF en code. Depuis que vous voulez l'envoyer par e-mail, vous êtes connecté au réseau, vous pouvez donc utiliser le service Docmosis cloud, qui est en réalité un service de publipostage. Envoyez-lui les données/images à fusionner avec votre modèle. bénéficier de beaucoup moins de code et de décharger la plupart du traitement de votre application iPad.Je l’ai vu utilisé dans une application iPad et c’était Nice.

2
Paul Jowett