Embed, Flatten or Remove PDF Annotations in Swift for iOS
Use the PSPDFProcessor class to embed, flatten or remove annotations. Get additional resources by visiting iOS PDF Annotation Library.
//// Copyright © 2016-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
/// Shows how to embed, flatten and remove annotations with `PSPDFProcessor`.final class AnnotationProcessingExample: Example {
// MARK: Lifecycle
override init() { super.init()
title = "Annotation Processing" contentDescription = "Shows how to embed, flatten and remove annotations with PSPDFProcessor" category = .documentProcessing priority = 10 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { let document = AssetLoader.document(for: .welcome) return AnnotationProcessingPDFViewController(document: document) }}
/// Processes annotations of it's document.private final class AnnotationProcessingPDFViewController: PDFViewController {
// MARK: Lifecycle
override func commonInit(with document: Document?, configuration: PDFConfiguration) { super.commonInit(with: document, configuration: configuration)
let actions = [("Embed", #selector(embedAnnotations)), ("Flatten", #selector(flattenAnnotations)), ("Remove", #selector(removeAnnotations))] let barButtonItems = actions.map { title, selector in UIBarButtonItem(title: title, style: .plain, target: self, action: selector) } navigationItem.setRightBarButtonItems(barButtonItems.reversed(), for: .document, animated: false) }
// MARK: Bar Button Item Actions
/// Presents document with annotations embedded. @objc private func embedAnnotations() { let embeddedDocumentURL = FileHelper.temporaryPDFFileURL(prefix: "embedded")
// We want to embed annotations, i.e. keep them editable. guard processAnnotations(.embed, document: document!, newDocumentURL: embeddedDocumentURL) else { presentErrorMessage("Embedding annotations failed.") return }
presentProcessedDocument(embeddedDocumentURL) }
/// Presents document with annotations flattened. @objc private func flattenAnnotations() { let flattenedDocumentURL = FileHelper.temporaryPDFFileURL(prefix: "flattened")
// We want to flatten annotations, i.e. make them non-editable. guard processAnnotations(.flatten, document: document!, newDocumentURL: flattenedDocumentURL) else { presentErrorMessage("Flattening annotations failed.") return }
presentProcessedDocument(flattenedDocumentURL) }
/// Presents document with annotations removed. @objc private func removeAnnotations() { let removedAnnotationsDocumentURL = FileHelper.temporaryPDFFileURL(prefix: "removedAnnotations")
// We want to remove annotations. guard processAnnotations(.remove, document: document!, newDocumentURL: removedAnnotationsDocumentURL) else { presentErrorMessage("Removing annotations failed.") return }
presentProcessedDocument(removedAnnotationsDocumentURL) }
// MARK: Document Processing
/// Processes annotations. /// /// - Parameters: /// - annotationChange: Which `PSPDFAnnotationChange` to perform. /// - document: `Document` containing pages with annotations. /// - newDocumentURL: `URL` that's used as output file URL of `PSPDFProcessor`. /// - Returns: `true` iff processing succeeded, `false` otherwise. private func processAnnotations(_ annotationChange: AnnotationChange, document: Document, newDocumentURL: URL) -> Bool { // Set up configuration to flatten the document. guard let configuration = Processor.Configuration(document: document) else { print("Processor configuration needs a valid document") return false }
// Process all types of annotations. configuration.modifyAnnotations(ofTypes: .all, change: annotationChange)
// We are only interested in the pages that actually have annotations. configuration.includeOnlyIndexes(pagesWithAnnotations(document))
do { // Process annotations. // `PSPDFProcessor` doesn't modify the document, but creates an output file instead. let processor = Processor(configuration: configuration, securityOptions: nil) processor.delegate = self try processor.write(toFileURL: newDocumentURL) } catch { print("Error while processing document: \(error)") return false }
return true }
// MARK: Helper
/// Filters pages with annotations. /// /// - Parameter document: `Document` containing pages to filter. /// - Returns: `NSIndexSet` of pages with annotations. private func pagesWithAnnotations(_ document: Document) -> IndexSet { let pagesWithAnnotations = NSMutableIndexSet() let allTypesButLinkAndForms = Annotation.Kind.all.subtracting([.link, .widget])
for page in 0..<document.pageCount { if document.annotationsForPage(at: page, type: allTypesButLinkAndForms).isEmpty { continue } pagesWithAnnotations.add(Int(page)) }
return pagesWithAnnotations as IndexSet }
/// Presents a processed document. /// /// - Parameter processedDocumentURL: `URL` of processed document. private func presentProcessedDocument(_ processedDocumentURL: URL) { let processedDocument = Document(url: processedDocumentURL) let pdfController = PDFViewController(document: processedDocument) pdfController.navigationItem.setRightBarButtonItems([], for: .document, animated: false) let navigationController = UINavigationController(rootViewController: pdfController) present(navigationController, animated: true, completion: nil) }
/// Presents an error message. /// /// - Parameter message: Error message to present. private func presentErrorMessage(_ message: String) { let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .default)) self.present(alert, animated: true, completion: nil) }}
extension AnnotationProcessingPDFViewController: ProcessorDelegate { nonisolated func processor(_ processor: Processor, didProcessPage currentPage: UInt, totalPages: UInt) { 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.