SDK iOS - Tạo tệp PDF theo chương trình


79

Theo ý kiến ​​trung thực của tôi, việc sử dụng khung CoreGraphics là một công việc tẻ nhạt, khi nói đến việc vẽ một tệp PDF theo chương trình.

Tôi muốn tạo một tệp PDF theo chương trình , sử dụng các đối tượng khác nhau từ các chế độ xem trong ứng dụng của mình.

Tôi muốn biết liệu có bất kỳ hướng dẫn PDF hay nào cho iOS SDK hay không, có thể là thư viện giảm.

Tôi đã xem hướng dẫn này, Hướng dẫn tạo PDF , nhưng nó chủ yếu được viết bằng C. Tìm kiếm thêm kiểu Objective-C. Đây cũng có vẻ là một cách vô lý để ghi vào tệp PDF, phải tính toán vị trí các đường và các đối tượng khác sẽ được đặt.

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

CHỈNH SỬA: Đây là một ví dụ trên GitHub bằng cách sử dụng libHaru trong một dự án iPhone.


1
haha, tôi biết điều này là cũ, nhưng 100 trang pdf trên iPhone không là gì cả. nó sẽ gây căng thẳng cho máy chủ nhiều hơn so với thiết bị iOS, vì máy chủ sẽ phải nhân nó với số lượng người dùng đồng thời cố gắng làm điều đó.
Armand

Câu trả lời:


56

Một vài điều ...

Đầu tiên, có một lỗi trong việc tạo PDF CoreGraphics trong iOS dẫn đến các tệp PDF bị hỏng. Tôi biết sự cố này tồn tại cho đến và bao gồm cả iOS 4.1 (Tôi chưa thử nghiệm iOS 4.2). Sự cố liên quan đến phông chữ và chỉ hiển thị nếu bạn đưa văn bản vào tệp PDF của mình. Triệu chứng là, khi tạo tệp PDF, bạn sẽ thấy lỗi trong bảng điều khiển gỡ lỗi trông giống như sau:

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

Khía cạnh khó khăn là tệp PDF kết quả sẽ hiển thị tốt ở một số trình đọc PDF, nhưng không hiển thị ở những nơi khác. Vì vậy, nếu bạn có quyền kiểm soát phần mềm sẽ được sử dụng để mở tệp PDF của mình, bạn có thể bỏ qua vấn đề này (ví dụ: nếu bạn chỉ định hiển thị tệp PDF trên máy tính để bàn iPhone hoặc Mac, thì bạn có thể sử dụng CoreGraphics). Tuy nhiên, nếu bạn cần tạo một tệp PDF hoạt động ở mọi nơi, thì bạn nên xem xét kỹ vấn đề này. Đây là một số thông tin bổ sung:

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

Để giải quyết vấn đề này, tôi đã sử dụng libHaru thành công trên iPhone để thay thế cho thế hệ PDF CoreGraphics. Ban đầu, việc bắt libHaru xây dựng dự án của tôi hơi khó khăn một chút, nhưng khi tôi đã thiết lập dự án đúng cách, nó hoạt động tốt theo nhu cầu của tôi.

Thứ hai, tùy thuộc vào định dạng / bố cục của tệp PDF của bạn, bạn có thể cân nhắc sử dụng Trình tạo giao diện để tạo chế độ xem dùng làm "mẫu" cho đầu ra PDF của bạn. Sau đó, bạn sẽ viết mã để tải chế độ xem, điền vào bất kỳ dữ liệu nào (ví dụ: đặt văn bản cho UILabels, v.v.), sau đó hiển thị các phần tử riêng lẻ của chế độ xem vào PDF. Nói cách khác, sử dụng IB để xác định tọa độ, phông chữ, hình ảnh, vv và viết mã để render các yếu tố khác nhau (ví dụ như, UILabel, UIImageView, vv) trong một cách chung chung, do đó bạn không cần phải mã hóa cứng tất cả mọi thứ. Tôi đã sử dụng cách tiếp cận này và nó hoạt động tốt cho nhu cầu của tôi. Một lần nữa, điều này có thể có ý nghĩa hoặc không đối với tình huống của bạn tùy thuộc vào nhu cầu định dạng / bố cục của tệp PDF của bạn.

CHỈNH SỬA: (trả lời bình luận đầu tiên)

Việc triển khai của tôi là một phần của sản phẩm thương mại, nghĩa là tôi không thể chia sẻ mã đầy đủ, nhưng tôi có thể đưa ra một phác thảo chung:

Tôi đã tạo tệp .xib có chế độ xem và định kích thước chế độ xem thành 850 x 1100 (tệp PDF của tôi nhắm mục tiêu 8,5 x 11 inch, vì vậy điều này giúp dễ dàng dịch sang / từ tọa độ thời gian thiết kế).

Trong mã, tôi tải chế độ xem:

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

Sau đó, tôi điền vào các yếu tố khác nhau. Tôi đã sử dụng các thẻ để tìm các phần tử thích hợp, nhưng bạn có thể làm theo cách khác. Thí dụ:

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

Sau đó, tôi gọi một hàm để hiển thị chế độ xem sang tệp PDF. Hàm này đi một cách đệ quy hệ thống phân cấp chế độ xem và hiển thị từng chế độ xem con. Đối với dự án của tôi, tôi chỉ cần hỗ trợ Label, ImageView và View (cho các khung nhìn lồng nhau):

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

Ví dụ, đây là cách triển khai addImageView của tôi (các hàm HPDF_ là từ 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);
        }
    }
}

Hy vọng rằng điều đó cung cấp cho bạn ý tưởng.


Bạn có ví dụ nào về cách sử dụng XIB làm mẫu không?
WrightsCS

12

Đó là một câu trả lời muộn, nhưng vì tôi đã đấu tranh rất nhiều với việc tạo pdf, tôi cho rằng việc chia sẻ quan điểm của mình là điều đáng giá. Thay vì đồ họa Core, để tạo bối cảnh, bạn cũng có thể sử dụng các phương pháp UIKit để tạo pdf.

Apple đã ghi rõ điều đó trong hướng dẫn vẽ và in.


4
FWIW— trang web dành cho hướng dẫn đã ngừng hoạt động trong Godaddy.
CuriousRabbit

8

Các chức năng PDF trên iOS đều dựa trên CoreGraphics, điều này có ý nghĩa, bởi vì các bản vẽ nguyên thủy trong iOS cũng dựa trên CoreGraphics. Nếu bạn muốn hiển thị trực tiếp các bản gốc 2D thành PDF, bạn sẽ phải sử dụng CoreGraphics.

Có một số phím tắt cho các đối tượng cũng nằm trong UIKit, chẳng hạn như hình ảnh. Việc vẽ PNG sang ngữ cảnh PDF vẫn yêu cầu lệnh gọi đến CGContextDrawImage, nhưng bạn có thể thực hiện với CGImage mà bạn có thể lấy từ UIImage, ví dụ:

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

Nếu bạn muốn có thêm hướng dẫn về thiết kế tổng thể, vui lòng trình bày rõ ràng hơn về ý nghĩa của bạn về "các đối tượng khác nhau trong ứng dụng của bạn" và những gì bạn đang cố gắng hoàn thành.


Cảm ơn vi đa trả lơi. Theo các đối tượng, ý tôi đơn giản là tôi đang lấy văn bản do người dùng nhập vào và muốn đặt chúng ở các vị trí khác nhau trên PDF. Và như bạn đã chỉ ra, một số đồ họa. Một cái gì đó giống như một biểu mẫu có thể được gửi qua email.
WrightsCS

Xin chào, tôi có một câu hỏi: theo cách này (CGContextDrawImage), pdf không phải là vector nữa. Tôi nên làm gì nếu tôi muốn làm nhiều đồ họa vector vào trang duy nhất của một pdf, trong khi đó, chắc chắn rằng pdf cũng vector
Thiên đường

2

Muộn, nhưng có thể hữu ích cho người khác. Có vẻ như kiểu hoạt động của mẫu PDF có thể là cách bạn muốn hơn là xây dựng PDF bằng mã. Vì bạn vẫn muốn tắt nó qua email, bạn đã kết nối với mạng nên bạn có thể sử dụng dịch vụ đám mây Docmosis , đây là một dịch vụ trộn thư hiệu quả. Gửi cho nó dữ liệu / hình ảnh để hợp nhất với mẫu của bạn. Cách tiếp cận đó có lợi là ít mã hơn rất nhiều và giảm tải phần lớn quá trình xử lý từ ứng dụng iPad của bạn. Tôi đã thấy nó được sử dụng trong một ứng dụng iPad và nó rất hay.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.