Como converter UIView para PDF no iOS?


87

Existem muitos recursos sobre como exibir um PDF em um aplicativo UIView. O que estou trabalhando agora é criar um PDF a partir UIViews.

Por exemplo, eu tenho um UIView, com subviews como TextViews, UILabels, UIImages, assim como pode converter um grande UIView como um todo, incluindo todos os seus subviews e subsubviews a um PDF?

Eu verifiquei a referência do iOS da Apple . No entanto, ele fala apenas sobre como escrever pedaços de texto / imagem em um arquivo PDF.

O problema que estou enfrentando é que o conteúdo que desejo gravar em um arquivo como PDF é muito grande. Se eu escrever no PDF peça por peça, vai ser um trabalho enorme. É por isso que estou procurando uma maneira de escrever UIViewsPDFs ou mesmo bitmaps.

Eu tentei o código-fonte que copiei de outro Q / A no Stack Overflow. Mas isso só me dá um PDF em branco com o UIViewtamanho dos limites.

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

Respostas:


124

Observe que o método a seguir cria apenas um bitmap da visualização; não cria tipografia real.

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

Certifique-se também de importar: QuartzCore / QuartzCore.h


2
+1 Passei por vários posts sobre geração de PDF antes de encontrar esta solução simples.
Jason George

7
Eu queria fazer o mesmo e seu método parece funcionar bem, mas sua qualidade é muito baixa. Eu perdi alguma coisa?
iEamin

7
Eu suspeito que a qualidade está muito baixa porque ele está pegando o UIView e convertendo-o em um raster, onde como os outros métodos de renderização de texto e imagens os preserva diretamente como vetores no arquivo PDF.
joshaidan

3
Estou seguindo esse método, mas estou recebendo um pdf em branco gerado. Alguém pode me ajudar ?
Raj

Funciona muito bem !!! Felicidades !! o único problema que tenho é gerar PDF em apenas uma página. Como posso separar páginas em vez de ter um arquivo PDF longo?
Rudi,

25

Além disso, se alguém estiver interessado, aqui está o código 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)
    }
}

1
esta criação de PDF apenas para a primeira página! que tal o scrollview?
Saurabh Prajapati

Ótima pergunta! Não sou eu quem deve perguntar, no entanto. Talvez comece outra pergunta?
retrovius

Tenho o mesmo problema que @SaurabhPrajapati e criamos uma pergunta
Jonas Deichelmann

10

Se alguém estiver interessado, aqui está o código 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)
        }
    }

Sua instrução de guarda significa que UIGraphicsEndPDFContext () não é chamado - talvez adicione um adiamento antes?
David H

@DavidH obrigado, David, boa ideia! Além disso, acho que é uma boa ideia adicionar um tipo de bloco de conclusão completion: (success: Bool) -> ()para casos de retorno de guarda
Denis Rumiantsev

1
Ontem eu postei uma pergunta e resposta sobre como produzir uma imagem de alta resolução renderizando a visualização em uma imagem grande e, em seguida, desenhando a imagem em um PDF de interesse: stackoverflow.com/a/35442187/1633251
David H

5

Uma maneira super fácil de criar um PDF a partir do UIView é usar a extensão UIView

Swift 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 ""
    }
  }
}

Crédito: http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/


Obrigado, funcionou, uma pergunta, então eu tenho a visualização de rolagem longa, mas o arquivo PDF mostra apenas uma parte dela, então há uma maneira de ajustar seu código, por exemplo, para dar a ele altura?
Hussein Elbeheiry,

@HusseinElbeheiry apenas use contentView para gerar pdf. Quando eu criar um scrollView (UIScrollView), com certeza vou criar um contentView (UIView) e colocar o contentView no scrollView e adicionar todos os elementos subsequentes ao contentView. Nesse caso, basta usar contentView para criar um documento PDF. contentView.exportAsPdfFromView
iAleksandr

3

Com o Swift 5 / iOS 12, você pode combinar CALayero render(in:)método de com UIGraphicsPDFRenderero writePDF(to:withActions:)método de para criar um arquivo PDF a partir de uma UIViewinstância.


O seguinte código de amostra do Playground mostra como usar render(in:)e 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)")
}

Observação: para usar playgroundSharedDataDirectoryno seu Playground, primeiro você precisa criar uma pasta chamada "Dados compartilhados do Playground" na pasta "Documentos" do seu macOS.


A UIViewControllerimplementação completa da subclasse abaixo mostra uma maneira possível de refatorar o exemplo anterior para um aplicativo 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)")
        }
    }

}

2

Isso irá gerar PDF a partir do UIView e abrir a caixa de diálogo de impressão, objetivo C. Anexe - (IBAction)PrintPDF:(id)senderao seu botão na tela. Adicionar #import <QuartzCore/QuartzCore.h>estrutura

Arquivo H

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

Arquivo 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];

    }

}

-4

Não sei por que, mas a resposta de Casilic me dá uma tela em branco no iOS6.1. O código abaixo funciona.

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

16
Este é exatamente o mesmo código que minha resposta dividida em dois métodos separados ???? Como você acha que isso resolveu o problema da tela em branco quando é o mesmo código ??
casílico de

Eu tive a mesma experiência. Recebi um PDF em branco do primeiro código. Dividir em dois, como fez Alex, funcionou. Não posso explicar por quê.
Tom Tallak Solbu
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.