iOS SDK - Programmgesteuertes Generieren einer PDF-Datei

79

Die Verwendung des CoreGraphics- Frameworks ist meiner ehrlichen Meinung nach eine mühsame Arbeit, wenn es darum geht, eine PDF-Datei programmgesteuert zu zeichnen.

Ich möchte programmgesteuert ein PDF mit verschiedenen Objekten aus Ansichten in meiner App erstellen .

Ich bin interessiert zu wissen, ob es gute PDF-Tutorials für das iOS SDK gibt, vielleicht einen Drop in der Bibliothek.

Ich habe dieses Tutorial gesehen, das PDF-Erstellungs-Tutorial , aber es wurde größtenteils in C geschrieben. Auf der Suche nach mehr Objective-C-Stil. Dies scheint auch eine lächerliche Möglichkeit zu sein, in eine PDF-Datei zu schreiben und zu berechnen, wo Linien und andere Objekte platziert werden.

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

BEARBEITEN: Hier ist ein Beispiel für GitHub mit libHaru in einem iPhone-Projekt.

WrightsCS
quelle
1
haha, ich weiß, das ist alt, aber 100 Seiten PDF auf einem iPhone ist nichts. Der Server wird stärker belastet als das iOS-Gerät, da der Server dies mit der Anzahl der gleichzeitigen Benutzer multiplizieren muss, die dies versuchen.
Armand

Antworten:

56

Ein paar Dinge ...

Erstens gibt es einen Fehler bei der CoreGraphics PDF-Generierung in iOS, der zu beschädigten PDFs führt. Ich weiß, dass dieses Problem bis einschließlich iOS 4.1 besteht (ich habe iOS 4.2 nicht getestet). Das Problem hängt mit Schriftarten zusammen und wird nur angezeigt, wenn Sie Text in Ihre PDF-Datei aufnehmen. Das Symptom ist, dass beim Generieren der PDF-Datei Fehler in der Debug-Konsole angezeigt werden, die folgendermaßen aussehen:

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

Der schwierige Aspekt ist, dass das resultierende PDF in einigen PDF-Readern gut gerendert wird, an anderen Stellen jedoch nicht gerendert werden kann. Wenn Sie also die Kontrolle über die Software haben, die zum Öffnen Ihrer PDF-Datei verwendet wird, können Sie dieses Problem möglicherweise ignorieren (z. B. wenn Sie die PDF-Datei nur auf dem iPhone- oder Mac-Desktop anzeigen möchten, sollten Sie sie problemlos verwenden können CoreGraphics). Wenn Sie jedoch eine PDF-Datei erstellen müssen, die überall funktioniert, sollten Sie sich dieses Problem genauer ansehen. Hier einige zusätzliche Informationen:

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

Als Problemumgehung habe ich libHaru erfolgreich auf dem iPhone als Ersatz für die CoreGraphics PDF-Generierung verwendet. Es war anfangs etwas schwierig, libHaru mit meinem Projekt zu erstellen, aber sobald ich mein Projekt richtig eingerichtet hatte, funktionierte es gut für meine Bedürfnisse.

Zweitens können Sie je nach Format / Layout Ihrer PDF-Datei in Betracht ziehen, Interface Builder zu verwenden, um eine Ansicht zu erstellen, die als "Vorlage" für Ihre PDF-Ausgabe dient. Sie würden dann Code schreiben, um die Ansicht zu laden, Daten einzugeben (z. B. Text für UILabels festlegen usw.) und dann die einzelnen Elemente der Ansicht in das PDF zu rendern. (ZB mit anderen Worten, zu spezifizieren Verwendung IB Koordinaten, Schriften, Bildern, etc. und Code schreiben , um verschiedene Elemente zu machen UILabel, UIImageViewusw.) in allgemeiner Weise , so dass Sie hart Code alles nicht haben. Ich habe diesen Ansatz verwendet und er hat sich hervorragend für meine Bedürfnisse bewährt. Auch dies kann je nach Formatierungs- / Layoutanforderungen Ihrer PDF-Datei für Ihre Situation sinnvoll sein oder auch nicht.

EDIT: (Antwort auf 1. Kommentar)

Meine Implementierung ist Teil eines kommerziellen Produkts, was bedeutet, dass ich nicht den vollständigen Code freigeben kann, aber einen allgemeinen Überblick geben kann:

Ich habe eine .xib-Datei mit einer Ansicht erstellt und die Ansicht auf 850 x 1100 skaliert (meine PDF-Datei war auf 8,5 x 11 Zoll ausgerichtet, sodass die Übersetzung in / von Entwurfszeitkoordinaten einfach ist).

Im Code lade ich die Ansicht:

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

Ich fülle dann verschiedene Elemente aus. Ich habe Tags verwendet, um die entsprechenden Elemente zu finden, aber Sie können dies auch auf andere Weise tun. Beispiel:

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

Dann rufe ich eine Funktion auf, um die Ansicht in die PDF-Datei zu rendern. Diese Funktion durchläuft rekursiv die Ansichtshierarchie und rendert jede Unteransicht. Für mein Projekt muss ich nur Label, ImageView und View unterstützen (für verschachtelte Ansichten):

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

Als Beispiel ist hier meine Implementierung von addImageView (HPDF_-Funktionen stammen von 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);
        }
    }
}

Hoffentlich kommt Ihnen das auf die Idee.

cbranch
quelle
Haben Sie Beispiele für die Verwendung eines XIB als Vorlage?
WrightsCS
12

Es ist eine späte Antwort, aber da ich viel mit der PDF-Generierung zu kämpfen hatte, hielt ich es für sinnvoll, meine Ansichten zu teilen. Anstelle von Core-Grafiken können Sie zum Erstellen eines Kontexts auch UIKit-Methoden verwenden, um ein PDF zu generieren.

Apple hat dies im Zeichen- und Druckhandbuch gut dokumentiert .

Bani Uppal
quelle
4
FWIW - die Seite für das Tutorial ist für Godaddy eine Sackgasse.
CuriousRabbit
8

Die PDF-Funktionen unter iOS basieren alle auf CoreGraphics, was sinnvoll ist, da die Zeichenprimitive in iOS auch auf CoreGraphics basieren. Wenn Sie 2D-Grundelemente direkt in eine PDF-Datei rendern möchten, müssen Sie CoreGraphics verwenden.

Es gibt einige Verknüpfungen für Objekte, die ebenfalls in UIKit leben, wie z. B. Bilder. Das Zeichnen eines PNG in einen PDF-Kontext erfordert weiterhin den Aufruf von CGContextDrawImage, aber Sie können dies mit dem CGImage tun, das Sie von einem UIImage erhalten können, z.

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

Wenn Sie weitere Anleitungen zum Gesamtdesign wünschen, geben Sie bitte genauer an, was Sie unter "verschiedenen Objekten in Ihrer App" verstehen und was Sie erreichen möchten.

Ben Zotto
quelle
Danke für die Antwort. Mit Objekten meine ich einfach, dass ich vom Benutzer eingegebenen Text nehme und sie an verschiedenen Stellen im PDF platzieren möchte. Und wie Sie bereits betont haben, einige Grafiken. So etwas wie ein Formular, das per E-Mail verschickt werden kann.
WrightsCS
Hallo Mann, ich habe eine Frage: Auf diese Weise (CGContextDrawImage) ist das PDF kein Vektor mehr. Was soll ich tun, wenn ich mehrere Vektorgrafiken auf einer Seite eines PDFs rendern möchte? In der Zwischenzeit muss sichergestellt werden, dass das PDF auch ein Vektor ist
Paradise
2

Spät, aber möglicherweise hilfreich für andere. Es hört sich so an, als wäre eine PDF-Vorlage der gewünschte Ansatz, anstatt die PDF-Datei im Code zu erstellen. Da Sie es trotzdem per E-Mail versenden möchten, sind Sie mit dem Netz verbunden, sodass Sie so etwas wie den Docmosis- Clouddienst verwenden können, der praktisch ein Seriendruckdienst ist. Senden Sie ihm die Daten / Bilder, um sie mit Ihrer Vorlage zusammenzuführen. Diese Art von Ansatz hat den Vorteil, dass viel weniger Code vorhanden ist und der größte Teil der Verarbeitung von Ihrer iPad-App abgeladen wird. Ich habe gesehen, dass es in einer iPad-App verwendet wurde und es war schön.

Paul Jowett
quelle