2010-12-06 83 views
76

使用CoreGraphics框架是枯燥的工作,在我誠實的看來,當涉及到以編程方式繪製PDF文件。iOS SDK - 以編程方式生成一個PDF文件

我想以編程方式創建一個PDF,在我的應用程序中使用視圖中的各種對象。

我很想知道是否有任何有關iOS SDK的優秀PDF教程,也許是庫中的一個下拉菜單。

我見過這個教程,PDF Creation Tutorial,但它主要是用C編寫的。尋找更多的Objective-C風格。這也似乎是寫入PDF文件的一種荒謬的方式,不得不計算線路和其他對象的放置位置。

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

編輯:這裏是在iPhone項目上GitHub using libHaru一個例子。

+1

哈哈已記錄得很好,我知道這是舊的,但在iPhone上100頁的PDF是什麼。它會比iOS設備更強調服務器,因爲服務器將不得不乘以嘗試這樣做的併發用戶的數量。 – Armand 2015-09-26 21:10:57

回答

56

一對夫婦的事情...

首先,是與CoreGraphics中生成PDF在iOS設備導致損壞的PDF文件中的錯誤。我知道這個問題存在和包括iOS 4.1(我還沒有測試iOS 4.2)。該問題與字體有關,只有在您的PDF中包含文字時纔會顯示。該症狀是,生成PDF文件時,你會看到在調試控制檯看起來像這樣的錯誤:

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

棘手的方面是,生成的PDF將使一些的PDF閱讀器不錯,但不能在其他地方渲染。因此,如果您可以控制將用於打開PDF的軟件,則可以忽略此問題(例如,如果您只打算在iPhone或Mac桌面上顯示PDF,那麼您應該很好地使用CoreGraphics中)。但是,如果您需要創建可在任何地方使用的PDF,那麼您應該仔細看看這個問題。下面是一些額外的信息:

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

作爲一種變通方法,我用libHaru成功地在iPhone上爲CoreGraphics中生成PDF的替代品。讓libHaru最初與我的項目一起構建起來有點棘手,但是一旦我的項目設置正確,它就可以很好地滿足我的需求。其次,根據PDF的格式/佈局,您可能會考慮使用Interface Builder創建一個視圖,作爲PDF輸出的「模板」。然後,您將編寫代碼來加載視圖,填寫任何數據(例如,爲UILabels設置文本等),然後將視圖的各個元素渲染到PDF中。換句話說,使用IB來指定座標,字體,圖像等,並編寫代碼以通用的方式渲染各種元素(例如,UILabel,UIImageView等),因此您不必硬編碼所有內容。我使用了這種方法,它對我的​​需求很有幫助。同樣,這可能會或可能不會對您的情況有意義,具體取決於PDF的格式/佈局需求。

編輯:(針對第一個評論)

我的實現是一個商業產品意味着我不能共享的完整代碼的一部分,但我可以給一個大致輪廓:

我創建一個帶有視圖的.xib文件,並將視圖大小設置爲850 x 1100(我的PDF定位爲8.5 x 11英寸,因此可以很容易地翻譯到設計時座標)。

在代碼中,我加載視圖:

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

我然後填入各種元素。我使用標籤來查找適當的元素,但是您可以通過其他方式執行此操作。例如:

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

然後我調用一個函數來將視圖渲染到PDF文件。此函數遞歸地遍歷視圖層次結構並呈現每個子視圖。對於我的項目,我需要支持只有標籤,ImageView的,和視圖(嵌套視圖):

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

作爲一個例子,這是我實現addImageView的(HPDF_功能是從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); 
     } 
    } 
} 

希望這給你的想法。

+0

您是否有任何關於如何將XIB用作模板的示例? – WrightsCS 2010-12-06 05:14:46

8

iOS上的PDF功能都是基於CoreGraphics的,這很有意義,因爲iOS中的繪圖基元也是基於CoreGraphics的。如果您想將2D基元直接渲染到PDF中,則必須使用CoreGraphics。

還有一些生活在UIKit中的對象的快捷方式,例如圖像。繪製PNG爲PDF方面仍然需要調用CGContextDrawImage,但你可以用CGImage,你可以從一個UIImage得到的,如做:

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

如果您想對整體設計更多的指導,請更清楚地說明你的意思是「整個應用中的各種對象」以及你想要完成的事情。

+0

感謝您的回覆。通過對象,我只是指我正在接收用戶輸入的文本,並希望將它們放置在PDF上的各個位置。正如你所指出的,一些圖形。就像可以通過電子郵件發送的表單一樣。 – WrightsCS 2010-12-06 03:21:58

+0

嗨,我有一個問題:通過這種方式(CGContextDrawImage),pdf不再是矢量。如果我想將多個矢量圖形渲染到pdf的單頁中,我該怎麼做,同時確保pdf也是矢量 – Paradise 2015-12-25 07:59:29

2

晚了,但可能對他人有幫助。這聽起來像一個PDF模板的操作風格可能是你想要的方法,而不是在代碼中構建PDF。因爲無論如何您都要通過電子郵件發送郵件,因此您已連接到網絡,因此您可以使用類似Docmosis雲服務的郵件合併服務。將數據/圖像發送給您的模板進行合併。這種方法的好處是代碼少很多,並且可以從iPad應用程序中卸載大部分處理。我已經看到它在iPad應用程序中使用,它很好。

12

這是一個很晚的答覆,但由於我與pdf一代掙扎了很多,我認爲值得分享我的觀點。爲了創建上下文,您可以使用UIKit方法來生成pdf,而不是Core graphics。

蘋果在drawing and printing guide.