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.