如何在 iOS 中将 UIView 转换为 PDF?

Posted

技术标签:

【中文标题】如何在 iOS 中将 UIView 转换为 PDF?【英文标题】:How to Convert UIView to PDF within iOS? 【发布时间】:2011-03-26 15:06:29 【问题描述】:

有很多关于如何在应用程序的UIView 中显示 PDF 的资源。我现在正在做的是从UIViews 创建一个 PDF。

例如,我有一个UIView,有像 Textviews、UILabelsUIImages 这样的子视图,那么如何将一个 big UIView 转换为一个整体,包括它的所有子视图以及 PDF 的子视图?

我已经检查了Apple's ios reference。但是,它只涉及将文本/图像写入 PDF 文件。

我面临的问题是我想以 PDF 格式写入文件的内容很多。如果我将它们逐个写入 PDF,这将是一项巨大的工作。 这就是为什么我正在寻找一种将UIViews 写入 PDF 甚至位图的方法。

我已经尝试过从 Stack Overflow 的其他问答中复制的源代码。但它只给了我一个空白 PDF,其边界大小为 UIView

-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename

    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [NSMutableData data];

    // Points the pdf converter to the mutable data object and to the UIView to be converted
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();

    // draws rect to the view and thus this is captured by UIGraphicsBeginPDFContextToData
    [aView drawRect:aView.bounds];

    // remove PDF rendering context
    UIGraphicsEndPDFContext();

    // Retrieves the document directories from the iOS device
    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];

    // instructs the mutable data object to write its context to a file on disk
    [pdfData writeToFile:documentDirectoryFilename atomically:YES];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);

【问题讨论】:

【参考方案1】:

请注意,以下方法只是创建视图的位图;它不会创建实际的排版。

(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename

    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [NSMutableData data];

    // Points the pdf converter to the mutable data object and to the UIView to be converted
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();


    // draws rect to the view and thus this is captured by UIGraphicsBeginPDFContextToData

    [aView.layer renderInContext:pdfContext];

    // remove PDF rendering context
    UIGraphicsEndPDFContext();

    // Retrieves the document directories from the iOS device
    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];

    // instructs the mutable data object to write its context to a file on disk
    [pdfData writeToFile:documentDirectoryFilename atomically:YES];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);

还要确保导入: QuartzCore/QuartzCore.h

【讨论】:

+1 在找到这个简单的解决方案之前,我浏览了几篇关于 pdf 生成的帖子。 我也想做同样的事情,你的方法似乎效果很好,但质量很低。我错过了什么吗? 我怀疑质量非常低,因为它使用 UIView 并将其转换为光栅,而其他渲染文本和图像的方法直接将它们保存为 PDF 文件中的矢量。 我正在遵循这种方法,但我得到了一个空白的 pdf 生成。谁能帮帮我? 效果很好!!!干杯!!我唯一的问题是,它只在一页中生成 PDF。我怎样才能分隔页面而不是长 PDF 文件?!【参考方案2】:

另外,如果有人感兴趣,这里是 Swift 3 代码:

func createPdfFromView(aView: UIView, saveToDocumentsWithFileName fileName: String)

    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil)
    UIGraphicsBeginPDFPage()

    guard let pdfContext = UIGraphicsGetCurrentContext() else  return 

    aView.layer.render(in: pdfContext)
    UIGraphicsEndPDFContext()

    if let documentDirectories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first 
        let documentsFileName = documentDirectories + "/" + fileName
        debugPrint(documentsFileName)
        pdfData.write(toFile: documentsFileName, atomically: true)
    

【讨论】:

只为首页创建 PDF!滚动视图呢? 好问题!然而,我不是要问的人。也许开始另一个问题? 我遇到了同样的问题,然后@SaurabhPrajapati 我创建了一个Question【参考方案3】:

如果有人感兴趣,这里是 Swift 2.1 代码:

    func createPdfFromView(aView: UIView, saveToDocumentsWithFileName fileName: String)
    
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil)
        UIGraphicsBeginPDFPage()

        guard let pdfContext = UIGraphicsGetCurrentContext() else  return 

        aView.layer.renderInContext(pdfContext)
        UIGraphicsEndPDFContext()

        if let documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first 
            let documentsFileName = documentDirectories + "/" + fileName
            debugPrint(documentsFileName)
            pdfData.writeToFile(documentsFileName, atomically: true)
        
    

【讨论】:

你的保护声明意味着 UIGraphicsEndPDFContext() 没有被调用 - 也许更早添加一个延迟? @DavidH 谢谢,大卫,好主意!另外,我认为,为保护返回案例添加一个完成块类型 completion: (success: Bool) -> () 是个好主意 昨天我发布了一个关于如何通过在大图像中渲染视图然后将图像绘制成 PDF 来生成高分辨率图像的问答:***.com/a/35442187/1633251【参考方案4】:

从 UIView 创建 PDF 的一种超级简单的方法是使用 UIView 扩展

斯威夫特 4.2

extension UIView 

  // Export pdf from Save pdf in drectory and return pdf file path
  func exportAsPdfFromView() -> String 

      let pdfPageFrame = self.bounds
      let pdfData = NSMutableData()
      UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
      UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)
      guard let pdfContext = UIGraphicsGetCurrentContext() else  return "" 
      self.layer.render(in: pdfContext)
      UIGraphicsEndPDFContext()
      return self.saveViewPdf(data: pdfData)

  

  // Save pdf file in document directory
  func saveViewPdf(data: NSMutableData) -> String   
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let docDirectoryPath = paths[0]
    let pdfPath = docDirectoryPath.appendingPathComponent("viewPdf.pdf")
    if data.write(to: pdfPath, atomically: true) 
        return pdfPath.path
     else 
        return ""
    
  

信用:http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/

【讨论】:

谢谢它的工作,一个问题,所以我有长滚动视图,但 PDF 文件只显示了它的一部分,所以有没有办法调整你的代码,例如给它高度? @HusseinElbeheiry 只需使用 contentView 生成 pdf。当我创建一个scrollView(UIScrollView)时,我肯定会创建一个contentView(UIView)并将contentView放在scrollView中,并将所有后续元素添加到contentView中。在这种情况下,使用 contentView 创建 PDF 文档就足够了。 contentView.exportAsPdfFromView 在哪里可以找到保存的 PDF 文件? @MidhunNarayan 在您的应用文档目录中。只需在控制台上打印 pdfPath 并访问它。 @AshishChauhan 我已经打印了路径,但是当我打开我的文件应用程序时它没有显示。我需要什么额外的东西才能在我的文件应用程序中查看转换后的 pdf【参考方案5】:

在 Swift 5 / iOS 12 中,您可以将 CALayerrender(in:) 方法与 UIGraphicsPDFRendererwritePDF(to:withActions:) 方法结合使用,以便从 UIView 实例创建 PDF 文件。


以下 Playground 示例代码展示了如何使用 render(in:)writePDF(to:withActions:)

import UIKit
import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .orange
let subView = UIView(frame: CGRect(x: 20, y: 20, width: 40, height: 60))
subView.backgroundColor = .magenta
view.addSubview(subView)

let outputFileURL = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("MyPDF.pdf")
let pdfRenderer = UIGraphicsPDFRenderer(bounds: view.bounds)

do 
    try pdfRenderer.writePDF(to: outputFileURL, withActions:  context in
        context.beginPage()
        view.layer.render(in: context.cgContext)
    )
 catch 
    print("Could not create PDF file: \(error)")

注意:要在 Playground 中使用 playgroundSharedDataDirectory,首先需要在 macOS 的“Documents”文件夹中创建一个名为“Shared Playground Data”的文件夹。


下面的UIViewController 子类完整实现展示了一种可能的方式来重构前面的iOS 应用示例:

import UIKit

class ViewController: UIViewController 

    override func viewDidLoad() 
        super.viewDidLoad()

        let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        view.backgroundColor = .orange
        let subView = UIView(frame: CGRect(x: 20, y: 20, width: 40, height: 60))
        subView.backgroundColor = .magenta
        view.addSubview(subView)

        createPDF(from: view)
    

    func createPDF(from view: UIView) 
        let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let outputFileURL = documentDirectory.appendingPathComponent("MyPDF.pdf")
        print("URL:", outputFileURL) // When running on simulator, use the given path to retrieve the PDF file

        let pdfRenderer = UIGraphicsPDFRenderer(bounds: view.bounds)

        do 
            try pdfRenderer.writePDF(to: outputFileURL, withActions:  context in
                context.beginPage()
                view.layer.render(in: context.cgContext)
            )
         catch 
            print("Could not create PDF file: \(error)")
        
    


【讨论】:

在 iPhone 中我可以找到这个文件,使用文件应用程序它没有显示【参考方案6】:

这将从 UIView 生成 PDF 并打开打印对话框,目标 C。 将- (IBAction)PrintPDF:(id)sender 附加到屏幕上的按钮。 添加#import <QuartzCore/QuartzCore.h>框架

H 文件

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    
    UIPrintInteractionController *printController;
    

- (IBAction)PrintPDF:(id)sender;

M 文件

-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename


    NSMutableData *pdfData = [NSMutableData data];

    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();


    [aView.layer renderInContext:pdfContext];
    UIGraphicsEndPDFContext();

    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];
    NSString *file = [documentDirectory stringByAppendingPathComponent:@"yourPDF.pdf"];
    NSURL *urlPdf = [NSURL fileURLWithPath: file];

    [pdfData writeToFile:documentDirectoryFilename atomically:YES];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);




- (IBAction)PrintPDF:(id)sender

    [self createPDFfromUIView:self.view saveToDocumentsWithFileName:@"yourPDF.pdf"];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"yourPDF.pdf"];
    NSData *myData = [NSData dataWithContentsOfFile: path];

    UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
    if(pic && [UIPrintInteractionController canPrintData: myData] ) 

        pic.delegate = self;

        UIPrintInfo *printInfo = [UIPrintInfo printInfo];
        printInfo.outputType = UIPrintInfoOutputGeneral;
        printInfo.jobName = [path lastPathComponent];
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        pic.printInfo = printInfo;
        pic.showsPageRange = YES;
        pic.printingItem = myData;

        void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) = ^(UIPrintInteractionController *pic, BOOL completed, NSError *error) 
            //self.content = nil;
            if(!completed && error)

                NSLog(@"Print Error: %@", error);
            
        ;

        [pic presentAnimated:YES completionHandler:completionHandler];

    


【讨论】:

【参考方案7】:

我不知道为什么,但 casilic 的回答让我在 iOS6.1 上出现黑屏。下面的代码有效。

-(NSMutableData *)createPDFDatafromUIView:(UIView*)aView 

    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [NSMutableData data];

    // Points the pdf converter to the mutable data object and to the UIView to be converted
    UIGraphicsBeginPDFContextToData(pdfData, aView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();


    // draws rect to the view and thus this is captured by UIGraphicsBeginPDFContextToData

    [aView.layer renderInContext:pdfContext];

    // remove PDF rendering context
    UIGraphicsEndPDFContext();

    return pdfData;



-(NSString*)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename

    // Creates a mutable data object for updating with binary data, like a byte array
    NSMutableData *pdfData = [self createPDFDatafromUIView:aView];

    // Retrieves the document directories from the iOS device
    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];

    // instructs the mutable data object to write its context to a file on disk
    [pdfData writeToFile:documentDirectoryFilename atomically:YES];
    NSLog(@"documentDirectoryFileName: %@",documentDirectoryFilename);
    return documentDirectoryFilename;

【讨论】:

这与我的答案完全相同,只是用两种不同的方法分解出来????当它是相同的代码时,你认为这如何解决了你的黑屏问题?? 我也有同样的经历。从第一个代码中得到一个空白 PDF。像亚历克斯那样把它分成两部分,让它工作。无法解释原因。

以上是关于如何在 iOS 中将 UIView 转换为 PDF?的主要内容,如果未能解决你的问题,请参考以下文章

在IOS中将字节流转换为PDF、html、css文件

如何在iOS中将输出(UIView)导出为指定大小的图像

如何在 Android 中将 PDF 页面转换为图像?

如何在 C# 中将 PDF 转换为图像? [复制]

如何在 C# 中将 .docx 转换为 .pdf [关闭]

如何在 libreoffice 6.4 中将 pdf 转换为 docx?