Comment convertir UIView en PDF dans iOS?

87

Il existe de nombreuses ressources sur la façon d'afficher un PDF dans une application UIView. Ce sur quoi je travaille maintenant, c'est de créer un PDF à partir de UIViews.

Par exemple, j'ai un UIView, avec comme subviews Textviews, UILabels, UIImages, alors comment puis - je convertir un grand UIView dans son ensemble , y compris tous ses sous - vues et subsubviews à un PDF?

J'ai vérifié la référence iOS d'Apple . Cependant, il ne parle que de l'écriture de morceaux de texte / image dans un fichier PDF.

Le problème auquel je suis confronté est que le contenu que je souhaite écrire dans un fichier au format PDF est important. Si je les écris dans le PDF pièce par pièce, ce sera un travail énorme à faire. C'est pourquoi je recherche un moyen d'écrire UIViewsdans des PDF ou même des bitmaps.

J'ai essayé le code source que j'ai copié à partir d'autres questions / réponses dans Stack Overflow. Mais cela ne me donne qu'un PDF vierge avec la UIViewtaille des 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
la source

Réponses:

124

Notez que la méthode suivante crée juste une image bitmap de la vue; il ne crée pas de typographie réelle.

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

Assurez-vous également d'importer: QuartzCore / QuartzCore.h

casillique
la source
2
+1 J'ai parcouru plusieurs articles sur la génération de pdf avant de trouver cette solution simple.
Jason George
7
Je voulais faire de même et votre méthode semble bien fonctionner mais sa qualité est très faible. Ai-je manqué quelque chose?
iEamin
7
Je soupçonne que la qualité est assez faible car il prend l'UIView et le convertit en raster, alors que les autres méthodes de rendu du texte et des images les préservent directement en tant que vecteurs dans le fichier PDF.
joshaidan
3
Je suis cette méthode, mais je reçois un pdf vierge généré. Quelqu'un peut-il m'aider ?
Raj
Cela fonctionne à merveille !!! à votre santé !! le seul problème que j'ai, c'est qu'il génère des PDF en une seule page. Comment puis-je séparer des pages au lieu d'avoir un long fichier PDF?!
Rudi
25

De plus, si quelqu'un est intéressé, voici le code 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)
    }
}
rétrovius
la source
1
cette création de PDF pour seulement firstPage! qu'en est-il de scrollview?
Saurabh Prajapati
Excellente question! Je ne suis pas celui à qui demander, cependant. Peut-être commencer une autre question?
retrovius
J'ai le même problème que @SaurabhPrajapati et j'ai créé une question
Jonas Deichelmann
10

Si quelqu'un est intéressé, voici le code 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
la source
Votre déclaration de garde signifie que UIGraphicsEndPDFContext () n'est pas appelé - peut-être ajouter un report plus tôt?
David H
@DavidH merci, David, bonne idée! De plus, je pense qu'il y a une bonne idée d'ajouter un type de bloc d'achèvement completion: (success: Bool) -> ()pour les cas de retour de garde
Denis Rumiantsev
1
Hier, j'ai posté des questions / réponses sur la façon de produire une image haute résolution en rendant la vue dans une grande image, puis en dessinant l'image dans un PDF intéressé: stackoverflow.com/a/35442187/1633251
David H
5

Un moyen très simple de créer un PDF à partir d'UIView utilise l'extension 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édit: http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/

Ashish Chauhan
la source
Merci, cela a fonctionné, une question, donc j'ai un long défilement, mais le fichier PDF n'en montre qu'une partie, alors existe-t-il un moyen de modifier votre code par exemple pour lui donner de la hauteur?
Hussein Elbeheiry
@HusseinElbeheiry utilise simplement contentView pour générer des PDF. Lorsque je crée un scrollView (UIScrollView), je vais certainement créer un contentView (UIView) et placer le contentView dans le scrollView, et j'ajoute tous les éléments suivants au contentView. Dans ce cas, il suffit d'utiliser contentView pour créer un document PDF. contentView.exportAsPdfFromView
iAleksandr
3

Avec Swift 5 / iOS 12, vous pouvez combiner CALayerla render(in:)méthode de avec UIGraphicsPDFRendererla writePDF(to:withActions:)méthode de pour créer un fichier PDF à partir d'une UIViewinstance.


L'exemple de code Playground suivant montre comment utiliser render(in:)et 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)")
}

Remarque: pour pouvoir l'utiliser playgroundSharedDataDirectorydans votre Playground, vous devez d'abord créer un dossier appelé "Shared Playground Data" dans votre dossier "Documents" macOS.


L' UIViewControllerimplémentation complète de la sous-classe ci-dessous montre un moyen possible de refactoriser l'exemple précédent pour une application 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
la source
2

Cela générera un PDF à partir d'UIView et ouvrira la boîte de dialogue d'impression, objectif C. Attachez- - (IBAction)PrintPDF:(id)sendervous à votre bouton à l'écran. Ajouter un #import <QuartzCore/QuartzCore.h>cadre

Fichier H

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

Fichier 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
la source
-4

Je ne sais pas pourquoi, mais la réponse de casilic me donne un écran vide sur iOS6.1. Le code ci-dessous fonctionne.

-(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
la source
16
C'est le même code exact que ma réponse vient de diviser en deux méthodes distinctes ???? Comment pensez-vous que cela a résolu votre problème d'écran vide quand il s'agit du même code?
casillic
J'ai vécu la même expérience. Vous avez un PDF vierge du premier code. Le diviser en deux comme Alex l'a fait, ça a marché. Je ne peux pas expliquer pourquoi.
Tom Tallak Solbu