This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/ios/samples/pdf-forms.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. 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-2026 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 PSPDFKit
import 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.