Jak przekonwertować UIView na PDF w iOS?

87

Istnieje wiele zasobów dotyczących sposobu wyświetlania pliku PDF w aplikacji UIView. Teraz pracuję nad utworzeniem pliku PDF z UIViews.

Na przykład, mam UIViewz subviews jak TextView, UILabels, UIImages, tak jak mogę przekonwertować Big UIView jako całość, łącznie ze wszystkimi jego subviews i subsubviews do PDF?

Sprawdziłem referencje Apple iOS . Jednak mówi tylko o zapisywaniu fragmentów tekstu / obrazu do pliku PDF.

Problem, z którym się zmagam, polega na tym, że treści, które chcę zapisać do pliku w formacie PDF, to dużo. Jeśli napiszę je do pliku PDF kawałek po kawałku, będzie to ogromna praca. Dlatego szukam sposobu na pisanie UIViewsdo plików PDF lub nawet bitmap.

Wypróbowałem kod źródłowy, który skopiowałem z innych pytań i odpowiedzi w ramach Stack Overflow. Ale daje mi tylko pusty plik PDF z UIViewrozmiarem granic.

-(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
źródło

Odpowiedzi:

124

Zauważ, że poniższa metoda tworzy tylko bitmapę widoku; nie tworzy rzeczywistej typografii.

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

Upewnij się również, że importujesz: QuartzCore / QuartzCore.h

casillic
źródło
2
+1 Przejrzałem kilka postów na temat generowania plików PDF, zanim znalazłem to proste rozwiązanie.
Jason George,
7
Chciałem zrobić to samo i twoja metoda wydaje się działać dobrze, ale jej jakość jest bardzo niska. Czy coś przegapiłem?
iEamin
7
Podejrzewam, że jakość jest dość niska, ponieważ pobiera UIView i konwertuje go do rastra, gdzie inne metody renderowania tekstu i obrazów bezpośrednio zachowują je jako wektory w pliku PDF.
joshaidan
3
Postępuję zgodnie z tą metodą, ale otrzymuję pusty plik PDF. czy ktoś może mi pomóc ?
Raj
Działa niesamowicie !!! Twoje zdrowie !! jedyny problem, jaki mam, polega na tym, że generuje plik PDF na jednej stronie. Jak mogę oddzielić strony zamiast długiego pliku PDF?
Rudi
25

Dodatkowo, jeśli ktoś jest zainteresowany, oto kod 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
źródło
1
to tworzenie plików PDF tylko dla pierwszej strony! a co z scrollviewem?
Saurabh Prajapati
Świetne pytanie! Jednak nie ja o to pytam. Może zacznij inne pytanie?
retrovius
Mam wtedy ten sam problem @SaurabhPrajapati i stworzyłem pytanie
Jonas Deichelmann,
10

Jeśli ktoś jest zainteresowany, oto kod 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
źródło
Twoja instrukcja guard oznacza, że ​​UIGraphicsEndPDFContext () nie jest wywoływana - może dodać odroczenie wcześniej?
David H
@DavidH dzięki, David, dobry pomysł! Myślę też, że dobrym pomysłem jest dodanie rodzaju bloku uzupełniania completion: (success: Bool) -> ()dla przypadków powrotu strażników
Denis Rumiantsev
1
Wczoraj opublikowałem pytanie i odpowiedzi na temat tworzenia obrazu w wysokiej rozdzielczości przez renderowanie widoku na dużym obrazie, a następnie rysowanie obrazu do pliku PDF, który jest zainteresowany: stackoverflow.com/a/35442187/1633251
David H
5

Bardzo łatwym sposobem tworzenia pliku PDF z UIView jest użycie rozszerzenia 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 ""
    }
  }
}

Źródło: http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/

Ashish Chauhan
źródło
Dzięki, że zadziałało, jedno pytanie, więc mam długi widok przewijania, ale plik PDF pokazuje tylko jego część, więc czy istnieje sposób na poprawienie kodu, na przykład, aby nadać mu wysokość?
Hussein Elbeheiry,
@HusseinElbeheiry po prostu użyj contentView do wygenerowania pliku PDF. Kiedy tworzę scrollView (UIScrollView), na pewno utworzę contentView (UIView) i umieszczę contentView w scrollView, a następnie dodaję wszystkie kolejne elementy do contentView. W takim przypadku do stworzenia dokumentu PDF wystarczy użyć contentView. contentView.exportAsPdfFromView
iAleksandr
3

Swift z iOS 5/12, można połączyć CALayer„s render(in:)metody z UIGraphicsPDFRenderer” s writePDF(to:withActions:)metody w celu utworzenia pliku PDF ze UIViewinstancji.


Poniższy przykładowy kod Playground pokazuje, jak używać render(in:)i 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)")
}

Uwaga: aby używać go playgroundSharedDataDirectoryw Playground, musisz najpierw utworzyć folder o nazwie „Shared Playground Data” w folderze „Documents” systemu macOS.


UIViewControllerPodklasa pełna realizacja poniżej przedstawia możliwy sposób byłaby poprzedni przykład dla aplikacji 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
źródło
2

Spowoduje to wygenerowanie pliku PDF z UIView i otwarcie okna dialogowego drukowania, cel C. Dołącz - (IBAction)PrintPDF:(id)senderdo przycisku na ekranie. Dodaj #import <QuartzCore/QuartzCore.h>strukturę

Plik H.

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

Plik 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
źródło
-4

Nie wiem dlaczego, ale odpowiedź Casilica daje mi pusty ekran na iOS6.1. Poniższy kod działa.

-(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
źródło
16
To jest ten sam dokładny kod, co moja odpowiedź, podzielona na dwie oddzielne metody ???? Jak myślisz, jak to rozwiązało problem z pustym ekranem, skoro jest to ten sam kod?
casillic
Miałem to samo doświadczenie. Otrzymałem pusty plik PDF z pierwszego kodu. Podzielenie go na pół, tak jak zrobił to Alex, sprawiło, że zadziałało. Nie potrafię wyjaśnić, dlaczego.
Tom Tallak Solbu