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);
}
Cullen SUN
fonte

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

casílico
fonte
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)
    }
}
retrovius
fonte
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)
        }
    }
Denis Rumiantsev
fonte
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/

Ashish Chauhan
fonte
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)")
        }
    }

}
Imanou Petit
fonte
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];

    }

}
magero
fonte
-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;
}
Alex Stone
fonte
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