2010-12-06 6 views
76

Mit dem CoreGraphics Framework ist mühsame Arbeit, in meiner ehrlichen Meinung, wenn es um das programmatische Zeichnen einer PDF-Datei kommt.iOS SDK - programmgesteuert generieren Sie eine PDF-Datei

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

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

Ich habe dieses Tutorial gesehen, PDF Creation Tutorial, aber es wurde meist in C geschrieben. Auf der Suche nach mehr Objective-C-Stil. Dies scheint auch eine lächerliche Art zu sein, in eine PDF-Datei zu schreiben, um 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); 
} 

EDIT: Hier ist ein Beispiel auf GitHub using libHaru in einem iPhone-Projekt.

+1

dokumentiert hat, ich weiß, das ist alt, aber 100 Seiten pdf auf einem iPhone ist nichts. Es belastet den Server stärker als das iOS-Gerät, da der Server dies mit der Anzahl der gleichzeitigen Benutzer multiplizieren muss. – Armand

Antwort

56

Ein paar Dinge ...

Erstens gibt es einen Fehler mit Generation Core Graphics PDF in iOS, die in beschädigten PDF-Dateien zur Folge hat. Ich weiß, dass dieses Problem bis einschließlich iOS 4.1 besteht (ich habe iOS 4.2 nicht getestet). Das Problem bezieht sich auf Schriftarten und wird nur angezeigt, wenn Sie Text in Ihre PDF-Datei einfügen. Das Symptom ist, dass, wenn der PDF-Generierung Sie Fehler in der Debug-Konsole sehen werden, die wie folgt aussehen:

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

Der schwierige Aspekt ist, dass die resultierende PDF in einigen PDF-Reader fein machen, aber nicht zu rendern an anderen Orten. 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) CoreGraphics). Wenn Sie jedoch ein PDF erstellen möchten, das ü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

Zur Umgehung des Problems habe ich Libharu erfolgreich auf dem iPhone als Ersatz für Core Graphics PDF-Erzeugung verwendet. Es war etwas schwierig libHaru mit meinem Projekt zu bauen, aber sobald ich mein Projekt richtig eingerichtet hatte, funktionierte es für meine Bedürfnisse.

Zweitens könnten Sie, abhängig vom Format/Layout Ihres PDFs, den Interface Builder 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, irgendwelche Daten eingeben (z. B. Text für UILabels setzen usw.) und dann die einzelnen Elemente der Ansicht in die PDF-Datei rendern.Mit anderen Worten, verwende IB, um Koordinaten, Schriftarten, Bilder usw. zu spezifizieren und Code zu schreiben, um verschiedene Elemente (z. B. UILabel, UIImageView usw.) generisch darzustellen, so dass du nicht alles fest codieren musst. Ich habe diesen Ansatz verwendet und es hat sich hervorragend für meine Bedürfnisse entwickelt. Auch dies kann für Ihre Situation sinnvoll sein oder auch nicht, abhängig von den Formatierungs- und Layoutanforderungen Ihres PDFs.

EDIT: (Antwort auf ersten Kommentar)

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

I erstellt eine XIB-Datei mit einer Ansicht und Größe der Ansicht auf 850 x 1100 (meine PDF-Ausrichtung war 8,5 x 11 Zoll, so dass es einfach zu/von Entwurfszeit Koordinaten zu übersetzen).

In 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 dann in verschiedenen Elemente füllen. Ich habe Tags verwendet, um die passenden Elemente zu finden, aber Sie könnten dies auch anders machen. Beispiel:

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

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

- (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 sind 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 gibt Ihnen das die Idee.

+0

Haben Sie Beispiele, wie Sie ein XIB als Vorlage verwenden können? – WrightsCS

8

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

Es gibt einige Abkürzungen für Objekte, die in UIKit ebenfalls leben, wie Bilder. eine PNG in einem PDF-Kontext Zeichnung bedarf noch den Aufruf an CGContextDrawImage, aber man kann es mit dem CGImage tun, was man von einem UIImage bekommen, zB:

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

Wenn Sie mehr Führung auf Gesamtdesign wünschen, bitte genauer, was Sie unter "verschiedene Objekte in Ihrer App" verstehen und was Sie erreichen möchten.

+0

Danke für die Antwort. Unter Objekten verstehe ich einfach, dass ich vom Benutzer eingegebene Texte verwende und sie an verschiedenen Stellen im PDF-Dokument ablegen möchte. Und wie du schon erwähnt hast, einige Grafiken. Etwas wie eine Form, die per E-Mail verschickt werden kann. – WrightsCS

+0

Hallo Mann, ich habe eine Frage: Auf diese Weise (CGContextDrawImage) ist das PDF kein Vektor mehr. Was soll ich tun, wenn ich mehrere Vektorgrafiken in die einzelne Seite eines PDFs rendern möchte, in der Zwischenzeit sicherstellen, dass das PDF auch Vektor ist? – Paradise

2

Spät, aber möglicherweise hilfreich für andere. Es scheint, als ob ein PDF-Vorlagenstil für den Vorgang der gewünschte Ansatz ist, anstatt das PDF im Code zu erstellen. Da Sie es dennoch per E-Mail versenden möchten, sind Sie mit dem Internet verbunden, sodass Sie beispielsweise den Cloud-Dienst Docmosis verwenden können, der im Grunde genommen ein Seriendruckdienst ist. Senden Sie ihm die Daten/Bilder, die mit Ihrer Vorlage zusammengeführt werden sollen. Diese Art von Ansatz hat den Vorteil von viel weniger Code und entlastet den Großteil der Verarbeitung von Ihrer iPad App. Ich habe es in einer iPad App verwendet und es war nett.

12

Es ist eine späte Antwort, aber da ich viel mit PDF-Generierung gerungen habe, 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 eine PDF-Datei zu generieren.

von Apple es auch in der haha ​​drawing and printing guide.

+4

FWIW- Die Site für das Tutorial ist in Godaddy beendet. – CuriousRabbit