PDF form examples using Swift for iOS
This example shows the various ways you can work with PDF forms — from appearance customization to accessing form field data or working with digital signatures. Get additional resources by visiting our PDF form library for iOS.
//// Copyright © 2017-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
// MARK: Digital signing process
@MainActor class FormDigitalSigningExample: Example {
override init() { super.init() title = "Digital signing process" category = .forms priority = 15 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController? { let p12URL = AssetLoader.assetURL(for: "John Appleseed Private Key.p12") guard let p12data = try? Data(contentsOf: p12URL) else { print("Error reading p12 data from \(String(describing: p12URL))") self.showAlert(title: "Error reading p12 data file", on: delegate.currentViewController!.navigationController!) return nil } let p12 = PKCS12(data: p12data) let signatureManager = SDK.shared.signatureManager signatureManager.clearTrustedCertificates()
// Add certs to trust store for the signature validation process let certURL = AssetLoader.assetURL(for: "John Appleseed Public Key.p7c") let certData = try? Data(contentsOf: certURL) let certificates = try? X509.certificates(fromPKCS7Data: certData!) for x509 in certificates! { signatureManager.addTrustedCertificate(x509) } let fileName = "\(UUID().uuidString).pdf" let url = URL(fileURLWithPath: NSTemporaryDirectory().appending(fileName))
Task { do { let unsignedDocument = AssetLoader.document(for: "Form.pdf") let signatureFormElement = unsignedDocument.annotations(at: 0, type: SignatureFormElement.self).first!
let (certificates, privateKey) = try p12.unlockCertificateChain(withPassword: "test") // Use the demo timestamping server endpoint. let timestampServerURL = URL(string: "https://tsa.our.services.nutrient-powered.io/")! let configuration = SigningConfiguration(dataSigner: privateKey, certificates: certificates, timestampSource: timestampServerURL) try await unsignedDocument.sign(formElement: signatureFormElement, configuration: configuration, outputDataProvider: FileDataProvider(fileURL: url)) delegate.currentViewController?.navigationController?.pushViewController(PDFViewController(document: Document(url: url)), animated: true) } catch { self.showAlert(title: "Couldn't add signature", message: "\(error)", on: delegate.currentViewController!.navigationController!) print(error) } } return nil }}
// MARK: - Digital signing process with custom appearance
@MainActor class FormDigitalSigningExampleCustomAppearanceExample: Example {
override init() { super.init() title = "Digital signing process with custom appearance" category = .forms priority = 16 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController? { // Load the private key. let p12URL = AssetLoader.assetURL(for: "John Appleseed Private Key.p12") guard let p12data = try? Data(contentsOf: p12URL) else { print("Error reading p12 data from \(String(describing: p12URL))") self.showAlert(title: "Error reading p12 data file", on: delegate.currentViewController!.navigationController!) return nil } let p12 = PKCS12(data: p12data) let signatureManager = SDK.shared.signatureManager signatureManager.clearTrustedCertificates()
// Add certs to trust store for the signature validation process let certURL = AssetLoader.assetURL(for: "John Appleseed Public Key.p7c") let certData = try? Data(contentsOf: certURL) let certificates = try? X509.certificates(fromPKCS7Data: certData!) for x509 in certificates! { signatureManager.addTrustedCertificate(x509) }
// Load the unsigned document to get accurate page info for the custom appearance stream. let unsignedDocument = AssetLoader.document(for: "Form.pdf") let signatureFormElement = unsignedDocument.annotations(at: 0, type: SignatureFormElement.self).first!
// Generate a custom PDF that we later use as appearance for the signature. let tempPDF = FileHelper.temporaryPDFFileURL(prefix: "appearance") let format = UIGraphicsPDFRendererFormat() // To fill the signature form element, use the same size. let pageRect = CGRect(x: 0, y: 0, width: signatureFormElement.boundingBox.width, height: signatureFormElement.boundingBox.height) let renderer = UIGraphicsPDFRenderer(bounds: pageRect, format: format) try? renderer.writePDF(to: tempPDF, withActions: { context in context.beginPage()
// draw a gradient let colors = [UIColor.systemBlue.cgColor, UIColor.systemTeal.cgColor] let colorLocations: [CGFloat] = [0.0, 1.0] guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: colorLocations) else { return } context.cgContext.drawLinearGradient(gradient, start: CGPoint.zero, end: CGPoint(x: pageRect.size.width, y: pageRect.size.height), options: [])
// draw text let text = "This is a custom PDF apperance" text.draw(at: CGPoint(x: 3, y: 3), withAttributes: [ NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12) ]) }) print("Custom apperance stream is stored in \(tempPDF.path)")
// Create a `PDFSignatureAppearance` that will be used for the signature appearance while signing. let appearanceStream = Annotation.AppearanceStream(fileURL: tempPDF) let signatureAppearance = PDFSignatureAppearance { builder in builder.appearanceMode = .signatureOnly builder.signatureWatermark = appearanceStream }
// Create URL for the signed document destination. let fileName = "\(UUID().uuidString).pdf" let signedDocURL = URL(fileURLWithPath: NSTemporaryDirectory().appending(fileName))
Task { do { // Access the private key and certificate for signing. let (certificates, privateKey) = try p12.unlockCertificateChain(withPassword: "test")
// Create the configuration to be used while signing. let configuration = SigningConfiguration(dataSigner: privateKey, certificates: certificates, appearance: signatureAppearance, reason: "I agree to the Contract Agreement terms.")
// Sign the document using the signing configuration and providing the output destination for the signed document. try await unsignedDocument.sign(formElement: signatureFormElement, configuration: configuration, outputDataProvider: FileDataProvider(fileURL: signedDocURL)) delegate.currentViewController?.navigationController?.pushViewController(PDFViewController(document: Document(url: signedDocURL)), animated: true) } catch { self.showAlert(title: "Couldn't add signature", message: "\(error)", on: delegate.currentViewController!.navigationController!) print(error) } }
return nil }}
// MARK: - Digital signing process using custom DataSigning
@MainActor class FormCustomDigitalSigningExample: Example {
override init() { super.init() title = "Digital signing process using custom signing" category = .forms priority = 15 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController? { let signatureManager = SDK.shared.signatureManager signatureManager.clearTrustedCertificates()
// Add certs to trust store for the signature validation process let certURL = AssetLoader.assetURL(for: "John Appleseed Public Key.p7c") let certData = try? Data(contentsOf: certURL) let publicCertificates = try? X509.certificates(fromPKCS7Data: certData!) for x509 in publicCertificates! { signatureManager.addTrustedCertificate(x509) } let fileName = "\(UUID().uuidString).pdf" let url = URL(fileURLWithPath: NSTemporaryDirectory().appending(fileName))
Task { do { let unsignedDocument = AssetLoader.document(for: "Form.pdf") let signatureFormElement = unsignedDocument.annotations(at: 0, type: SignatureFormElement.self).first!
let customSigner = CustomDataSigner() let configuration = SigningConfiguration(dataSigner: customSigner, certificates: publicCertificates!) try await unsignedDocument.sign(formElement: signatureFormElement, configuration: configuration, outputDataProvider: FileDataProvider(fileURL: url)) delegate.currentViewController?.navigationController?.pushViewController(PDFViewController(document: Document(url: url)), animated: true) } catch { self.showAlert(title: "Couldn't add signature", message: "\(error)", on: delegate.currentViewController!.navigationController!) print(error) } } return nil }
private class CustomDataSigner: DataSigning { func sign(unsignedData: Data, hashAlgorithm: PDFSignatureHashAlgorithm) async throws -> (signedData: Data, dataFormat: PSPDFKit.SignedDataFormat) { // Carry out your custom data signing. // We will use a private key here for the sake of this example. // However you can use your custom signing implementation that doesn't rely on a private key. let p12URL = AssetLoader.assetURL(for: "John Appleseed Private Key.p12") let p12data = try Data(contentsOf: p12URL) let p12 = PKCS12(data: p12data) let (_, privateKey) = try p12.unlockCertificateChain(withPassword: "test") return try await privateKey.sign(unsignedData: unsignedData, hashAlgorithm: hashAlgorithm) } }}
// MARK: - Programmatic form filling
class FormFillingExample: Example {
override init() { super.init() title = "Programmatic Form Filling" contentDescription = "Automatically fills out all forms in code." category = .forms priority = 30 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { let document = AssetLoader.document(for: "Form.pdf") document.annotationSaveMode = .disabled
// Get all form objects and fill them in. DispatchQueue.global(qos: .default).async(execute: {() -> Void in let formElements = document.annotations(at: 0, type: FormElement.self) for formElement in formElements { Thread.sleep(forTimeInterval: 0.8) // Always update the model on the main thread. DispatchQueue.main.async(execute: {() -> Void in if let textFieldElement = formElement as? TextFieldFormElement { let fieldName = textFieldElement.fieldName ?? "" if textFieldElement.inputFormat == .date { textFieldElement.contents = "01/01/2001" // Telephone_Home needs exactly 7 digits } else if fieldName == "Telephone_Home" { textFieldElement.contents = "0123456" // Social Security Number needs exactly 9 digits } else if fieldName == "SSN" { textFieldElement.contents = "012345678" // The other phone numbers need exactly 10 digits } else if fieldName == "Telephone_Work" || fieldName == "Emergency_Phone" { textFieldElement.contents = "0123456789" // All the other form fields don't have any special validation } else { textFieldElement.contents = "Test \(fieldName)" } } else if let buttonElement = formElement as? ButtonFormElement { buttonElement.toggleButtonSelectionState() } }) } }) return FormFillingPDFViewController(document: document) }}
private final class FormFillingPDFViewController: PDFViewController {
override func commonInit(with document: Document?, configuration: PDFConfiguration) { super.commonInit(with: document, configuration: configuration)
let saveCopy = UIBarButtonItem(title: "Save Copy", style: .plain, target: self, action: #selector(FormFillingPDFViewController.saveCopy(_:))) navigationItem.setLeftBarButtonItems([pdfController.closeButtonItem, saveCopy], animated: false) }
@objc private func saveCopy(_ sender: UIBarButtonItem) { // Create a copy of the document let tempURL = FileHelper.temporaryPDFFileURL(prefix: "copy_\(document?.fileURL?.lastPathComponent ?? "Form")") guard let documentURL = document?.fileURL else { return } try? FileManager.default.copyItem(at: documentURL, to: tempURL)
// Transfer form values let documentCopy = Document(url: tempURL) let annotations = document?.annotations(at: 0, type: FormElement.self) let annotationsCopy = documentCopy.annotations(at: 0, type: FormElement.self) assert(annotations?.count == annotationsCopy.count, "This example is built to only fill forms - don't add/remove annotations.") for (index, formElement) in (annotationsCopy.enumerated()) { formElement.contents = annotations?[index].contents } try? documentCopy.save() guard let path = documentCopy.fileURL?.path else { return } let alert = UIAlertController(title: "Success", message: "Document copy saved to \(path)", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .default)) self.present(alert, animated: true, completion: nil) }}
// MARK: - Interactive Form with a digital signature
class FormDigitallySignedModifiedExample: Example {
override init() { super.init() title = "Example of an Interactive Form with a Digital Signature" category = .forms priority = 10 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { let document = AssetLoader.document(for: "Signed Form.pdf")
// check if document is signed. if let signatureElement = document.annotations(at: 0, type: SignatureFormElement.self).first { print("Document is signed: \(signatureElement.isSigned) info: \(String(describing: signatureElement.signatureInfo))") }
return PDFViewController(document: document) }}
// MARK: - Form with formatted text fields
class FormWithFormattingExample: Example {
override init() { super.init() title = "PDF Form with formatted text fields" category = .forms priority = 50 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { let document = AssetLoader.document(for: "Formatted Form Fields.pdf") return PDFViewController(document: document) }}
// MARK: - Read-only form
class FormWithFormattingReadonlyExample: Example {
override init() { super.init() title = "Readonly Form" category = .forms priority = 51 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { let document = AssetLoader.document(for: "Formatted Form Fields.pdf") return PDFViewController(document: document) { var editableAnnotationTypes = $0.editableAnnotationTypes editableAnnotationTypes?.remove(.widget) $0.editableAnnotationTypes = editableAnnotationTypes } }}
// MARK: - Programmatically fill form and save
class FormFillingAndSavingExample: Example {
override init() { super.init() title = "Programmatically fill form and save" category = .forms priority = 150 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { // Get the example form and copy it to a writable location. let document = AssetLoader.writableDocument(for: "Form.pdf", overrideIfExists: true) document.annotationSaveMode = .embedded
for formElement: FormElement in (document.formParser?.forms)! { if formElement is ButtonFormElement { (formElement as? ButtonFormElement)?.select() } else if formElement is ChoiceFormElement { (formElement as? ChoiceFormElement)?.selectedIndices = NSIndexSet(index: 1) as IndexSet } else if formElement is TextFieldFormElement { formElement.contents = "Test" } }
document.save { result in switch result { case .failure(let error): print("Error while saving: \(String(describing: error.localizedDescription))")
case .success: print("File saved correctly to \(document.fileURL!.path)") } }
return PDFViewController(document: document) }}
// MARK: - Programmatically create a text form field
class FormCreationExample: Example {
override init() { super.init() title = "Programmatically create a text form field" category = .forms priority = 160 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { // Get the example form and copy it to a writable location. let document = AssetLoader.writableDocument(for: "Form.pdf", overrideIfExists: true) document.annotationSaveMode = .embedded
// Create a new text field form element. let textFieldFormElement = TextFieldFormElement() textFieldFormElement.boundingBox = CGRect(x: 200, y: 100, width: 200, height: 20) textFieldFormElement.pageIndex = 0
// Insert a form field for the form element. It will automatically be added to the document. let textFormField = try! TextFormField.insertedTextField(withFullyQualifiedName: "name", documentProvider: document.documentProviders.first!, formElement: textFieldFormElement) print("Text form field created successfully: \(textFormField)")
return PDFViewController(document: document) }}
// MARK: - Programmatically reset some fields of a form PDF
class FormResetExample: Example {
override init() { super.init() title = "Programmatically reset some fields of a form PDF" category = .forms priority = 160 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { // Get the example form and copy it to a writable location. let document = AssetLoader.writableDocument(for: "Form.pdf", overrideIfExists: true) document.annotationSaveMode = .embedded
let lastNameField = document.formParser?.findField(withFullFieldName: "Last Name") if lastNameField != nil { lastNameField!.value = "Appleseed" } let firstNameField = document.formParser?.findField(withFullFieldName: "First Name") if firstNameField != nil { firstNameField!.value = "John" } if let checkBox = document.formParser?.findField(withFullFieldName: "HIGH SCHOOL DIPLOMA") as? ButtonFormField { checkBox.toggleButton(checkBox.annotations.first!) } // This should reset "High School Diploma" to default (unchecked), but "First name" and "Last name" keep their modified values. try! document.formParser?.resetForm([lastNameField!, firstNameField!], withFlags: .includeExclude)
return PDFViewController(document: document) }}
// MARK: - Programmatically create a push button form field with a custom image
class PushButtonCreationExample: Example {
override init() { super.init() title = "Programmatically create a push button form field with a custom image" category = .forms priority = 170 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { // Get the example form and copy it to a writable location. let document = AssetLoader.writableDocument(for: "Form.pdf", overrideIfExists: true) document.annotationSaveMode = .disabled
// Create a push button and position them in the document. let pushButtonFormElement = ButtonFormElement() pushButtonFormElement.boundingBox = CGRect(x: 20, y: 200, width: 100, height: 83) pushButtonFormElement.pageIndex = 0
// Add a URL action. pushButtonFormElement.action = URLAction(urlString: "https://www.nutrient.io/")
// Create a new appearance characteristics and set its normal icon. let appearanceCharacteristics = AppearanceCharacteristics() appearanceCharacteristics.normalIcon = UIImage(named: "exampleimage.jpg") pushButtonFormElement.appearanceCharacteristics = appearanceCharacteristics
// Insert a form field for the form element. It will automatically be added to the document. let pushButtonFormField = try! ButtonFormField.insertedButtonField(with: .pushButton, fullyQualifiedName: "PushButton", documentProvider: document.documentProviders.first!, formElements: [pushButtonFormElement], buttonValues: ["PushButton"]) print("Button form field created successfully: \(pushButtonFormField)")
return PDFViewController(document: document) }}
fileprivate extension Example {
func showAlert(title: String? = nil, message: String? = nil, on viewController: UIViewController) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK", style: .cancel)) viewController.present(alertController, animated: true) }}
This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.