Generate a PDF report using Swift for iOS
Generate a document out of multiple PDF files, add new pages, and protect the new PDF document with a password. Get additional resources by visiting our PDF generation library for iOS.
////  Copyright © 2018-2025 PSPDFKit GmbH. All rights reserved.////  The Nutrient sample applications are licensed with a modified BSD license.//  Please see License for details. This notice may not be removed from this file.//
import PSPDFKitimport PSPDFKitUI
final class ReportPDFGenerationExample: Example {    var statusHUDItem: StatusHUDItem?
    // MARK: Lifecycle
    override init() {        super.init()
        title = "Generate a PDF Report"        contentDescription = "Generate a PDF document on a mobile device without any server use."        category = .documentGeneration        priority = 1    }
    override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController? {        // Get base document        let document = AssetLoader.document(for: .annualReport)        guard let configuration = Processor.Configuration(document: document) else {            fatalError("Processor configuration needs a valid document")        }
        // Keep only the first and the last page of the original document.        configuration.removePages(IndexSet(1..<Int(document.pageCount - 1)))
        // Add a newly created single-paged document as the second page of the report        let pageInfo = document.pageInfoForPage(at: 0)!        let secondPageDocument = self.generateSecondPage(pageInfo: pageInfo)        let secondPageTemplate = PageTemplate(document: secondPageDocument, sourcePageIndex: 0)        configuration.addNewPage(at: 1, configuration: PDFNewPageConfiguration(pageTemplate: secondPageTemplate, builderBlock: nil))
        // Add a new page with a pattern grid as the third page of the report.        configuration.addNewPage(at: 2, configuration: PDFNewPageConfiguration(pageTemplate: PageTemplate(pageType: .tiledPatternPage, identifier: .grid5mm)) {            $0.backgroundColor = .white        })
        // Add a page from an existing document.        let welcomeDocument = AssetLoader.document(for: .welcome)        let welcomeTemplate = PageTemplate(document: welcomeDocument, sourcePageIndex: 7)        configuration.addNewPage(at: 3, configuration: PDFNewPageConfiguration(pageTemplate: welcomeTemplate, builderBlock: nil))
        // Scale the recently added page to the first page size        configuration.scalePage(3, to: pageInfo.size)
        // Draw "Generated for John Doe. Page X" on every page        self.drawWatermark(name: "John Doe", configuration: configuration)
        // Flatten all annotations.        configuration.modifyAnnotations(ofTypes: .all, change: .flatten)
        // Set owner password to only allow printing        let ownerPassword = "test123"        let documentSecurityOptions = try? Document.SecurityOptions(ownerPassword: ownerPassword, userPassword: nil, keyLength: Document.SecurityOptionsKeyLengthAutomatic, permissions: [.printing])        let processedDocumentURL = FileHelper.temporaryPDFFileURL(prefix: "processed")
        statusHUDItem = StatusHUDItem.progress(withText: PSPDFKit.localizedString("Preparing") + ("…"))        statusHUDItem?.push(animated: true, on: delegate.currentViewController?.view.window)
        DispatchQueue.global(qos: .default).async(execute: {() -> Void in            // Process annotations.            // `PSPDFProcessor` doesn't modify the document, but creates an output file instead.            let processor = Processor(configuration: configuration, securityOptions: documentSecurityOptions)            processor.delegate = self            try! processor.write(toFileURL: processedDocumentURL)
            DispatchQueue.main.async(execute: { [weak self] () -> Void in                guard let self else {                    return                }
                self.statusHUDItem?.pop(animated: true)                self.statusHUDItem = nil                // The newly processed Document.                let processedDocument = Document(url: processedDocumentURL)                processedDocument.title = "Generated PDF Report"                let pdfController = PDFViewController(document: processedDocument)                pdfController.navigationItem.rightBarButtonItems = [pdfController.annotationButtonItem, pdfController.searchButtonItem, pdfController.activityButtonItem]                delegate.currentViewController?.navigationController?.pushViewController(pdfController, animated: true)                self.presentSuccessAlert(viewController: pdfController)            })        })
        return nil    }
    // MARK: Private    private func generateSecondPage(pageInfo: PDFPageInfo) -> Document {        // Create a separate single-paged document, which will be added as the second page of the report.        let secondPageConfiguration = Processor.Configuration()        let blankPageConfiguration = PDFNewPageConfiguration(pageTemplate: PageTemplate.blank) {            $0.backgroundColor = UIColor.psc_systemBackground            $0.pageSize = pageInfo.size        }        secondPageConfiguration.addNewPage(at: 0, configuration: blankPageConfiguration)
        // Invoke processor to create new document.        let processor = Processor(configuration: secondPageConfiguration, securityOptions: nil)        processor.delegate = self        let data = try? processor.data()
        // Create the document which will be the report's second page        let secondPageDocument = Document(dataProviders: [DataContainerProvider(data: data!)])
        // Create a free text annotation as the title of the second page.        let titleFreeTextAnnotation = FreeTextAnnotation()        titleFreeTextAnnotation.boundingBox = CGRect(x: 228, y: 924, width: 600, height: 80)        titleFreeTextAnnotation.contents = "Some Annotations"        titleFreeTextAnnotation.fontSize = 40
        // Create a vector stamp annotation on the second page.        let logoURL = AssetLoader.assetURL(for: "PSPDFKit Logo.pdf")
        let vectorStamp = StampAnnotation()        vectorStamp.boundingBox = CGRect(x: 50, y: 724, width: 200, height: 200)        vectorStamp.appearanceStreamGenerator = FileAppearanceStreamGenerator(fileURL: logoURL)
        // Create a free text annotation which describes the vector stamp        let vectorStampDescriptionFreeTextAnnotation = FreeTextAnnotation()        vectorStampDescriptionFreeTextAnnotation.contents = "The logo above is a vector stamp annotation."        vectorStampDescriptionFreeTextAnnotation.boundingBox = CGRect(x: 67, y: 620, width: 600, height: 80)        vectorStampDescriptionFreeTextAnnotation.fontSize = 18
        // Create an image stamp annotation on the second page.        let imageStamp = StampAnnotation()        imageStamp.image = UIImage(named: "exampleimage.jpg")        imageStamp.boundingBox = CGRect(x: 60, y: 400, width: (imageStamp.image?.size.width)! / 4, height: (imageStamp.image?.size.height)! / 4)
        // Create a free text annotation which describes the image stamp        let imageStampDescriptionFreeTextAnnotation = FreeTextAnnotation()        imageStampDescriptionFreeTextAnnotation.contents = "The image above is an image stamp annotation."        imageStampDescriptionFreeTextAnnotation.boundingBox = CGRect(x: 67, y: 290, width: 600, height: 80)        imageStampDescriptionFreeTextAnnotation.fontSize = 18
        // Add annotations to the newly processed document.        secondPageDocument.add(annotations: [titleFreeTextAnnotation, vectorStamp, vectorStampDescriptionFreeTextAnnotation, imageStamp, imageStampDescriptionFreeTextAnnotation])
        guard let flattenedSecondPageConfiguration = Processor.Configuration(document: secondPageDocument) else {            fatalError("Processor configuration needs a valid document")        }
        // Flatten all annotations        flattenedSecondPageConfiguration.modifyAnnotations(ofTypes: .all, change: .flatten)        let flattenedSecondPageOutputFileURL = FileHelper.temporaryPDFFileURL(prefix: "flattened-second-page")        let secondPageProcessor = Processor(configuration: flattenedSecondPageConfiguration, securityOptions: nil)        secondPageProcessor.delegate = self        try? secondPageProcessor.write(toFileURL: flattenedSecondPageOutputFileURL)
        return Document(url: flattenedSecondPageOutputFileURL)    }
    private func drawWatermark(name: String, configuration: Processor.Configuration) {        let renderDrawBlock: PDFRenderDrawBlock = { context, page, cropBox, _ in            // Careful, this code is executed on background threads. Only use thread-safe drawing methods.            let text = "Generated for \(name). Page \(page + 1)"            let stringDrawingContext = NSStringDrawingContext()            stringDrawingContext.minimumScaleFactor = 0.1
            let attributes: [NSAttributedString.Key: Any] = [                .font: UIFont.boldSystemFont(ofSize: 30),                .foregroundColor: UIColor.red.withAlphaComponent(0.5)            ]            // Add text in the bottom center            context.translateBy(x: (cropBox.size.width / 2) - 230, y: cropBox.size.height - 100)            text.draw(with: cropBox, options: .usesLineFragmentOrigin, attributes: attributes, context: stringDrawingContext)        }        // Draw at the bottom of all pages.        configuration.drawOnAllCurrentPages(renderDrawBlock)    }
    private func presentSuccessAlert(viewController: UIViewController) {        let message = "1. Keep only the first and the last page of the original document.\n2. Add a newly created single-paged document as the second page of the report.\n3. Add a new page with a pattern grid as the third page of the report.\n4. Add a page from an existing document on disk.\n5. Draw watermark on every page.\n6. Flatten all annotations.\n7. Set owner password to only allow printing."        let alert = UIAlertController(title: "Successful PDF Report Generation", message: message, preferredStyle: .alert)        alert.addAction(UIAlertAction(title: "Dismiss", style: .default))        viewController.present(alert, animated: true, completion: nil)    }}
extension ReportPDFGenerationExample: ProcessorDelegate {    func processor(_ processor: Processor, didProcessPage currentPage: UInt, totalPages: UInt) {        statusHUDItem?.progress = CGFloat((currentPage + 1) / totalPages)        print("Progress: \(currentPage + 1) of \(totalPages)")    }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.