¿Cómo convertir UIView a PDF en iOS?

87

Hay muchos recursos sobre cómo mostrar un PDF en una aplicación UIView. En lo que estoy trabajando ahora es en crear un PDF a partir de UIViews.

Por ejemplo, tengo una UIView, con subvistas como Textviews, UILabels, UIImages, así que ¿cómo puedo convertir un grande UIView en su conjunto incluyendo todas sus subvistas y subsubviews a un PDF?

He comprobado la referencia de iOS de Apple . Sin embargo, solo habla de escribir fragmentos de texto / imagen en un archivo PDF.

El problema al que me enfrento es que el contenido que quiero escribir en un archivo como PDF es mucho. Si los escribo en el PDF pieza por pieza, será un gran trabajo por hacer. Por eso estoy buscando una forma de escribir UIViewsen PDF o incluso en mapas de bits.

Probé el código fuente que copié de otro Q / A dentro de Stack Overflow. Pero solo me da un PDF en blanco con el UIViewtamaño de los límites.

-(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 SOL
fuente

Respuestas:

124

Tenga en cuenta que el siguiente método crea solo un mapa de bits de la vista; no crea una tipografía 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);
}

También asegúrese de importar: QuartzCore / QuartzCore.h

casillic
fuente
2
+1 Revisé varias publicaciones sobre la generación de pdf antes de encontrar esta solución simple.
Jason George
7
Quería hacer lo mismo y su método parece funcionar bien, pero su calidad es muy baja. ¿Yo me perdí algo?
iEamin
7
Sospecho que la calidad es bastante baja porque está tomando UIView y convirtiéndolo en un ráster, mientras que los otros métodos de renderizado de texto e imágenes los conservan directamente como vectores en el archivo PDF.
Joshaidan
3
Estoy siguiendo este método, pero se genera un pdf en blanco. Alguien puede ayudarme ?
Raj
¡Funciona increíble! salud !! el único problema que tengo es que genera PDF en una sola página. ¿Cómo puedo separar páginas en lugar de tener un archivo PDF largo?
Rudi
25

Además, si alguien está interesado, aquí está el 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)
    }
}
retrovio
fuente
1
esta creación de PDF solo para firstPage! ¿qué pasa con scrollview?
Saurabh Prajapati
¡Gran pregunta! Sin embargo, no soy yo quien debe preguntar. ¿Quizás empezar otra pregunta?
retrovius
Tengo el mismo problema entonces @SaurabhPrajapati y creé una pregunta
Jonas Deichelmann
10

Si alguien está interesado, aquí está el 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
fuente
Su declaración de guardia significa que no se llama a UIGraphicsEndPDFContext (), ¿tal vez agregar un aplazamiento antes?
David H
@DavidH gracias, David, ¡buena idea! Además, creo que es una buena idea agregar un tipo de bloque de finalización completion: (success: Bool) -> ()para los casos de devolución de guardia
Denis Rumiantsev
1
Ayer publiqué una sesión de preguntas y respuestas sobre cómo producir una imagen de alta resolución renderizando la vista en una imagen grande y luego dibujando la imagen en un PDF si está interesado: stackoverflow.com/a/35442187/1633251
David H
5

Una forma súper fácil de crear un PDF desde UIView es usar la extensión UIView

Rápido 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
fuente
Gracias, funcionó. Una pregunta. Tengo una vista de desplazamiento larga, pero el archivo PDF solo muestra una parte, así que, ¿hay alguna manera de modificar el código, por ejemplo, para darle Altura?
Hussein Elbeheiry
@HusseinElbeheiry solo usa contentView para generar pdf. Cuando creo un scrollView (UIScrollView) definitivamente crearé un contentView (UIView) y pondré el contentView en el scrollView, y agrego todos los elementos posteriores al contentView. En este caso, basta con utilizar contentView para crear un documento PDF. contentView.exportAsPdfFromView
iAleksandr
3

Con Swift 5 / iOS 12, puede combinar CALayerel render(in:)método de UIGraphicsPDFRenderer' con el método de ' writePDF(to:withActions:)para crear un archivo PDF a partir de una UIViewinstancia.


El siguiente código de muestra de Playground muestra cómo usar render(in:)y 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)")
}

Nota: para poder usarlo playgroundSharedDataDirectoryen su área de juegos, primero debe crear una carpeta llamada "Datos compartidos de juegos" en la carpeta "Documentos" de macOS.


La UIViewControllerimplementación completa de la subclase a continuación muestra una forma posible de refactorizar el ejemplo anterior para una aplicación 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
fuente
2

Esto generará PDF desde UIView y abrirá el cuadro de diálogo de impresión, objetivo C. Adjuntar - (IBAction)PrintPDF:(id)sendera su botón en la pantalla. Agregar #import <QuartzCore/QuartzCore.h>marco

Archivo H

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

Archivo 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
fuente
-4

No sé por qué, pero la respuesta de casilic me deja una pantalla en blanco en iOS6.1. El siguiente código 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
fuente
16
Este es el mismo código exacto que mi respuesta acaba de dividir en dos métodos separados ???? ¿Cómo crees que esto solucionó el problema de la pantalla en blanco cuando es el mismo código?
casillic
Tuve la misma experiencia. Obtuve un PDF en blanco del primer código. Dividirlo en dos como hizo Alex, lo hizo funcionar. No puedo explicar por qué.
Tom Tallak Solbu