iOS SDK-programowo Generuj plik PDF

Korzystanie z frameworka CoreGraphics to, moim zdaniem, żmudna praca, jeśli chodzi o programowe rysowanie pliku PDF.

Chciałbym programowo utworzyć PDF , używając różnych obiektów z widoków w całej mojej aplikacji.

Jestem zainteresowany, czy są jakieś dobre samouczki PDF dla iOS SDK, może kropla w bibliotece.

Widziałem ten tutorial, PDF Creation Tutorial , ale został napisany głównie w C. patrząc dla bardziej obiektywnego stylu-C. Wydaje się to również śmiesznym sposobem zapisu do pliku PDF, wymagającym obliczenia miejsca, w którym zostaną umieszczone linie i inne obiekty.

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: Oto przykład na GitHub using libHaru w projekcie iPhone.

Author: WrightsCS, 2010-12-06

4 answers

Kilka rzeczy...

Po pierwsze, jest błąd z CoreGraphics PDF generation w systemie iOS, który powoduje uszkodzone pliki PDF. Wiem, że ten problem istnieje do iOS 4.1 włącznie (nie testowałem iOS 4.2). Problem jest związany z czcionkami i pojawia się tylko wtedy, gdy do pliku PDF dołączony jest tekst. Objawem jest to, że podczas generowania pliku PDF zobaczysz błędy w konsoli debugowania, które wyglądają tak:

<Error>: can't get CIDs for glyphs for 'TimesNewRomanPSMT'
[7]}trudnym aspektem jest to, że wynikowy plik PDF będzie renderował dobrze w niektórych plikach PDF czytelników, ale nie renderują w innych miejscach. Tak więc, jeśli masz kontrolę nad oprogramowaniem, które będzie używane do otwierania pliku PDF, możesz zignorować ten problem (np. jeśli zamierzasz wyświetlać plik PDF tylko na komputerach iPhone lub Mac, powinieneś używać CoreGraphics). Jeśli jednak chcesz utworzyć plik PDF, który działa w dowolnym miejscu, powinieneś przyjrzeć się temu problemowi. Oto kilka dodatkowych info:

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

Jako obejście, z powodzeniem użyłem libHaru na iPhonie jako zamiennika CoreGraphics PDF generation. To było trochę trudne, aby libHaru budował z moim projektem początkowo, ale kiedy już poprawnie skonfigurowałem mój projekt, działał dobrze dla moich potrzeb.

Po drugie, w zależności od formatu / układu pliku PDF, możesz rozważyć użycie Interface Builder do tworzenia widoku, który służy jako "szablon" dla pliku wyjściowego PDF. Następnie możesz napisać kod, aby załadować widok, wypełnić dowolne dane (np. ustawić tekst dla UILabels, itp.), a następnie renderować poszczególne elementy widoku do pliku PDF. Innymi słowy, użyj IB do określenia współrzędnych, czcionek, obrazów itp. i napisać kod do renderowania różnych elementów (np., UILabel, UIImageView, itd.) w sposób ogólny, dzięki czemu nie musisz wszystkiego kodować. Zastosowałem to podejście i wyszło świetnie dla mojego potrzeb. Ponownie, może to lub nie ma sensu dla twojej sytuacji, w zależności od potrzeb formatowania / układu pliku PDF.

EDIT: (odpowiedź na 1 komentarz)

Moja implementacja jest częścią produktu komercyjnego, co oznacza, że nie mogę udostępnić pełnego kodu, ale mogę podać ogólny zarys:

Stworzyłem .plik xib z widokiem i rozmiarem widoku do 850 x 1100 (mój plik PDF był ukierunkowany na 8,5 x 11 cali, więc ułatwia to tłumaczenie na / z czasu projektowania współrzędne).

W kodzie Wczytuję widok:

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

Następnie uzupełniam różne elementy. Użyłem tagów, aby znaleźć odpowiednie elementy, ale można to zrobić w inny sposób. Przykład:

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

Następnie wywołuję funkcję renderującą widok do pliku PDF. Ta funkcja rekurencyjnie porusza się po hierarchii widoku i renderuje każdy subview. W przypadku mojego projektu muszę obsługiwać tylko Label, ImageView i View (dla zagnieżdżonych widoków):

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

Jako przykład, oto moja implementacja addImageView (funkcje HPDF_ pochodzą z 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);
        }
    }
}
Mam nadzieję, że to Ci podpowie.
 56
Author: cbranch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-03 10:07:23

To późna odpowiedź, ale ponieważ dużo zmagałem się z generowaniem plików pdf, uznałem, że warto podzielić się moimi poglądami. Zamiast grafiki podstawowej, aby utworzyć kontekst, można również użyć metod UIKit do wygenerowania pliku pdf.

Apple dobrze to udokumentowało w Instrukcji rysowania i drukowania.

 12
Author: Bani Uppal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-06 01:01:29

Funkcje PDF na iOS są oparte na CoreGraphics, co ma sens, ponieważ prymitywy rysowania w iOS są również oparte na CoreGraphics. Jeśli chcesz renderować prymitywy 2D bezpośrednio do pliku PDF, musisz użyć CoreGraphics.

Istnieje kilka skrótów dla obiektów, które również żyją w UIKit, takich jak obrazy. Rysowanie kontekstu PNG NA PDF nadal wymaga wywołania CGContextDrawImage, ale możesz to zrobić za pomocą CGImage, który możesz uzyskać z interfejsu UIImage, np.:

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

Jeśli chcesz więcej wskazówek na temat ogólnego projektu, proszę być bardziej wyraźne o tym, co masz na myśli przez "różne obiekty w całej aplikacji" i co próbujesz osiągnąć.

 8
Author: Ben Zotto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-06 02:58:10

Późno, ale być może pomocne dla innych. Wygląda na to, że styl działania szablonu PDF może być pożądanym podejściem, a nie budowaniem pliku PDF w kodzie. Ponieważ i tak chcesz wysłać e-mail, jesteś podłączony do sieci, więc możesz użyć czegoś takiego jak usługa w chmurze Docmosis , która jest faktycznie usługą Mail-merge. Wyślij mu dane / obrazy, aby połączyć się z szablonem. Ten rodzaj podejścia ma tę zaletę, że znacznie mniej kodu i odciąża większość przetwarzania z aplikacji na iPada. Widziałem go używanego w aplikacji na iPada i było miło.

 2
Author: Paul Jowett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-02 14:15:00