Làm thế nào để chuyển đổi UIView sang PDF trong iOS?


87

Có rất nhiều tài nguyên về cách hiển thị PDF trong Ứng dụng UIView. Những gì tôi đang làm bây giờ là tạo một tệp PDF UIViews.

Ví dụ, tôi có một UIView, với subviews như TextView, UILabels, UIImages, vậy làm thế nào tôi có thể chuyển đổi một lớn UIView như một tổng thể bao gồm tất cả subviews và subsubviews của mình cho một PDF?

Tôi đã kiểm tra tham chiếu iOS của Apple . Tuy nhiên, nó chỉ nói về việc ghi các đoạn văn bản / hình ảnh vào tệp PDF.

Vấn đề tôi đang gặp phải là nội dung tôi muốn ghi vào một tệp dưới dạng PDF rất nhiều. Nếu tôi viết chúng thành từng đoạn PDF, đó sẽ là một công việc rất lớn phải làm. Đó là lý do tại sao tôi đang tìm cách ghi UIViewsvào PDF hoặc thậm chí là ảnh bitmap.

Tôi đã thử mã nguồn mà tôi đã sao chép từ Q / A khác trong Stack Overflow. Nhưng nó chỉ cung cấp cho tôi một tệp PDF trống với UIViewkích thước giới hạn.

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

Câu trả lời:


124

Lưu ý rằng phương pháp sau chỉ tạo một bitmap của dạng xem; nó không tạo ra kiểu chữ thực tế.

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

Ngoài ra, hãy đảm bảo rằng bạn nhập: QuartzCore / QuartzCore.h


2
+1 Tôi đã xem qua một số bài đăng về tạo pdf trước khi tìm thấy giải pháp đơn giản này.
Jason George

7
Tôi cũng muốn làm như vậy và phương pháp của bạn có vẻ hoạt động tốt nhưng chất lượng của nó rất thấp. Tôi có bỏ lỡ điều gì không?
iEamin

7
Tôi nghi ngờ chất lượng khá thấp vì nó đang sử dụng UIView và chuyển đổi nó thành raster, trong đó các phương pháp kết xuất văn bản và hình ảnh khác trực tiếp giữ chúng dưới dạng vectơ trong tệp PDF.
joshaidan

3
Tôi đang làm theo phương pháp này, nhưng tôi nhận được một pdf trống được tạo. Ai giúp tôi với ?
Raj

Nó hoạt động tuyệt vời !!! chúc mừng !! vấn đề duy nhất mà tôi gặp phải là nó tạo PDF chỉ trong một trang. Làm cách nào để tôi có thể Tách các trang thay vì có một tệp PDF dài ?!
Rudi

25

Ngoài ra, nếu ai quan tâm, đây là mã 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
việc tạo PDF này chỉ dành cho Trang đầu tiên! những gì về scrollview?
Saurabh Prajapati

Câu hỏi tuyệt vời! Tuy nhiên, tôi không phải là người phải hỏi. Có lẽ bắt đầu một câu hỏi khác?
retrovius

Tôi đã cùng một vấn đề thì @SaurabhPrajapati và tôi đã tạo ra một câu hỏi
Jonas Deichelmann

10

Nếu ai đó quan tâm, đây là mã 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)
        }
    }

Tuyên bố bảo vệ của bạn có nghĩa là UIGraphicsEndPDFContext () không được gọi - có thể thêm trì hoãn sớm hơn?
David H

@DavidH cảm ơn, David, ý kiến ​​hay! Ngoài ra, tôi nghĩ rằng, có một ý tưởng tốt thêm một loại khối hoàn thành completion: (success: Bool) -> ()đối với trường hợp trả lại bảo vệ
Denis Rumiantsev

1
Hôm qua, tôi đã đăng một câu hỏi và giải đáp về cách tạo ra hình ảnh có độ phân giải cao bằng cách hiển thị chế độ xem trong một hình ảnh lớn, sau đó vẽ hình ảnh đó thành một tệp PDF quan tâm: stackoverflow.com/a/35442187/1633251
David H

5

Một cách siêu dễ dàng để tạo tệp PDF từ UIView là sử dụng Tiện ích mở rộng 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 ""
    }
  }
}

Tín dụng: http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/


Cảm ơn nó đã hoạt động, Một câu hỏi, Vì vậy, tôi có chế độ xem cuộn dài, nhưng tệp PDF chỉ hiển thị một phần của nó, vậy có cách nào để điều chỉnh mã của bạn chẳng hạn để cung cấp cho nó Chiều cao không?
Hussein Elbeheiry

@HusseinElbeheiry chỉ cần sử dụng contentView để tạo pdf. Khi tôi tạo một scrollView (UIScrollView), tôi chắc chắn sẽ tạo một contentView (UIView) và đưa contentView vào scrollView, và tôi thêm tất cả các phần tử tiếp theo vào contentView. Trong trường hợp này, chỉ cần sử dụng contentView để tạo tài liệu PDF là đủ. contentView.exportAsPdfFromView
iAleksandr

3

Với Swift 5 / iOS 12, bạn có thể kết hợp phương thức CALayercủa 's render(in:)với UIGraphicsPDFRenderer' s writePDF(to:withActions:)để tạo tệp PDF từ một UIViewphiên bản.


Mã mẫu Playground sau đây cho biết cách sử dụng render(in:)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)")
}

Lưu ý: để sử dụng playgroundSharedDataDirectorytrong Playground của bạn, trước tiên bạn cần tạo một thư mục có tên là "Shared Playground Data" trong thư mục "Documents" của macOS.


Việc UIViewControllertriển khai hoàn chỉnh lớp con bên dưới cho thấy một cách khả thi để cấu trúc lại ví dụ trước đó cho ứng dụng 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

Thao tác này sẽ tạo PDF từ UIView và mở hộp thoại in, vật kính C. Đính kèm - (IBAction)PrintPDF:(id)sendervào nút của bạn trên màn hình. Thêm #import <QuartzCore/QuartzCore.h>khuôn khổ

H Tệp

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

Tệp 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

Tôi không biết tại sao, nhưng câu trả lời của casilic cho tôi màn hình trống trên iOS6.1. Đoạn mã dưới đây hoạt động.

-(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
Đây là mã chính xác giống như câu trả lời của tôi vừa được chia ra theo hai phương pháp riêng biệt ???? Làm thế nào để bạn nghĩ rằng điều này đã khắc phục sự cố màn hình trống của bạn khi nó là cùng một mã ??
casillic

Tôi đã có kinh nghiệm tương tự. Có một tệp PDF trống từ mã đầu tiên. Chia nó ra làm hai như Alex đã làm, làm cho nó hoạt động. Không thể giải thích tại sao.
Tom Tallak Solbu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.