Как конвертировать UIView в PDF в iOS?

87

Существует множество ресурсов о том, как отображать PDF-файлы в приложениях UIView. Сейчас я работаю над созданием PDF-файла UIViews.

Например, у меня есть UIView, с подвидами , как TextViews, UILabels, UIImages, так как я могу преобразовать масштабно UIView , как в целом , включая все его подвиды и subsubviews в формат PDF?

Я проверил ссылку на iOS от Apple . Однако здесь говорится только о записи фрагментов текста / изображения в файл PDF.

Проблема, с которой я столкнулся, заключается в том, что содержимого, которое я хочу записать в файл в формате PDF, очень много. Если я напишу их в PDF по частям, это будет огромной работой. Вот почему я ищу способ писать UIViewsв PDF-файлы или даже растровые изображения.

Я пробовал исходный код, который скопировал из других вопросов и ответов в Stack Overflow. Но он дает мне только пустой PDF-файл с UIViewограниченным размером.

-(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);
}
Каллен САН
источник

Ответы:

124

Обратите внимание, что следующий метод создает только растровое изображение представления; он не создает реальной типографики.

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

Также убедитесь, что вы импортировали: QuartzCore / QuartzCore.h

Casillic
источник
2
+1 Я просмотрел несколько сообщений о создании PDF-файлов, прежде чем нашел это простое решение.
Джейсон Джордж
7
Я хотел сделать то же самое, и ваш метод работает нормально, но его качество очень низкое. Я что-нибудь пропустил?
iEamin
7
Я подозреваю, что качество довольно низкое, потому что он берет UIView и конвертирует его в растр, тогда как другие методы рендеринга текста и изображений напрямую сохраняют их как векторы в файле PDF.
joshaidan
3
Я использую этот метод, но получаю пустой PDF-файл. Может кто-нибудь мне помочь ?
Raj
Работает офигенно !!! ура !! единственная проблема, которая у меня есть, это то, что он генерирует PDF всего на одной странице. Как я могу разделить страницы вместо длинного файла PDF ?!
Rudi
25

Кроме того, если кому-то интересно, вот код 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
это создание PDF только для firstPage! как насчет прокрутки?
Саураб Праджапати
Отличный вопрос! Однако спрашивать не я. Может, начнем еще один вопрос?
retrovius
У меня такая же проблема тогда, @SaurabhPrajapati, и я создал вопрос
Йонас Дайхельманн
10

Если кому-то интересно, вот код 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)
        }
    }
Денис Румянцев
источник
Ваш оператор защиты означает, что UIGraphicsEndPDFContext () не вызывается - может быть, добавить отсрочку раньше?
Дэвид Х
@DavidH спасибо, Дэвид, хорошая идея! Также, я думаю, есть хорошая идея добавить блок завершения completion: (success: Bool) -> ()для случаев возврата охранников
Денис Румянцев
1
Вчера я разместил вопросы и ответы о том, как создать изображение с высоким разрешением путем рендеринга представления в большом изображении, а затем интересующего его рисования в PDF: stackoverflow.com/a/35442187/1633251
Дэвид Х
5

Супер простой способ создать PDF-файл из UIView - использовать расширение 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 ""
    }
  }
}

Кредит: http://www.swiftdevcenter.com/create-pdf-from-uiview-wkwebview-and-uitableview/

Ашиш Чаухан
источник
Спасибо, это сработало. Один вопрос. Итак, у меня длинный режим прокрутки, но файл PDF показывает только его часть, так что есть способ настроить код, например, чтобы присвоить ему высоту?
Хусейн Эльбехейри
@HusseinElbeheiry просто используйте contentView для создания pdf. Когда я создаю scrollView (UIScrollView), я обязательно создаю contentView (UIView) и помещаю contentView в scrollView, а все последующие элементы добавляю в contentView. В этом случае для создания PDF-документа достаточно использовать contentView. contentView.exportAsPdfFromView
Александр
3

С Swift 5 / прошивкой 12, вы можете объединить CALayer«s render(in:)метод с UIGraphicsPDFRenderer» s writePDF(to:withActions:)метод для того , чтобы создать PDF - файл из UIViewэкземпляра.


В следующем примере кода игровой площадки показано, как использовать 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)")
}

Примечание: для использования playgroundSharedDataDirectoryна вашей игровой площадке вам сначала необходимо создать папку под названием «Общие данные игровой площадки» в папке «Документы» macOS.


UIViewControllerПодкласс полная реализация ниже показывает возможный способ реорганизовать предыдущий пример для приложения 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

Это сгенерирует PDF из UIView и откроет диалоговое окно печати, цель C. Прикрепите - (IBAction)PrintPDF:(id)senderк кнопке на экране. Добавить #import <QuartzCore/QuartzCore.h>фреймворк

H файл

    @interface YourViewController : UIViewController <MFMailComposeViewControllerDelegate,UIPrintInteractionControllerDelegate>

    {
    UIPrintInteractionController *printController;
    }

- (IBAction)PrintPDF:(id)sender;

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

Я не знаю почему, но ответ casilic дает мне пустой экран на iOS6.1. Код ниже работает.

-(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
Это тот же самый код, что и мой ответ, только что разбитый на два отдельных метода ???? Как вы думаете, это решило проблему с пустым экраном, когда это тот же код?
casillic
У меня был такой же опыт. Получил пустой PDF-файл из первого кода. Разделив его пополам, как это сделал Алекс, все заработало. Не могу объяснить почему.
Том Таллак Сольбу