Opened from the filesystem. The iOS API reference will be blank — it fetches its content and needs a real origin. Run ./serve.sh in this directory.

PDFCora for iOS

PDFCoraThe core: documents, pages, text, forms, annotations, signatures, redaction PDFCoraUIThe SwiftUI viewer and its toolbar, theme and review bar PDFCoraAIAnswers, summaries, proposals, alt text, remediation, diff

DocC is a single-page app: this reference only works over http, not file://. Run ./serve.sh in the documentation directory.

Install

Embed PDFCora.xcframework from a release, or add the package:

.package(url: "https://github.com/pdfcora/pdfcora-swift", from: "0.1.0")

The packages are not published yet. Until they are, build from source: see Getting a working checkout.

Open and render

import PDFCora

let document = try await PdfDocument(data: data)
let page = try await document.page(0)
let image = try await page.render(width: 1024, options: [.annotations, .forms])

Place a control over a form field

let transform = PdfViewTransform(page: try await page.size, fittingWidth: view.bounds.width)
for field in try await page.formFields() {
    let box = transform.toView(field.base.rect)   // top-left origin, ready for UIKit
}

Read a signature

for signature in try await document.signatures() {
    print(signature.subFilter ?? "?", signature.signingTime ?? "?")
    if !signature.coversWholeDocument {
        // Valid, but content was appended after signing and is not protected.
    }
}

Verify it

switch await document.verify(signature, options: .init(trustAnchors: roots)) {
case .valid(let signer, _):     print("signed by", signer.commonName ?? "?")
case .digestMismatch:           print("the bytes changed after signing")
case .untrusted(let chain, _):  print("intact, but not trusted", chain.count)
default: break
}

Integrity and trust are separate answers, and revocation is not checked.

Redact it

let report = try await document.redact([
    .region(rect, pageIndex: 0),
    .textMatches("account number"),
])
let clean = try await document.data(mode: .full)   // full, always

Content is removed and the page's content stream regenerated, so the words leave the file — a black box drawn over text would leave every one of them in it. Save with full; an incremental save does not write the regenerated stream at all. Images overlapping the region are removed whole rather than repainted, and a signed document cannot be redacted without breaking its signature.

Sign it

let signer = try Pkcs12Signer(pkcs12: p12, password: password)
let signed = try await document.sign(PdfSignOptions(
    signer: signer,
    reason: "Approved",
    timestampUrl: URL(string: "https://your-tsa.example/tsr")))   // optional, PAdES B-T

Signing appends an incremental update, so a signature already on the document keeps verifying. The first release signs invisibly: a signature is valid without a visible appearance. Signing uses the bytes the document was opened from, so edits made in memory, such as the redaction above, are not in the signed file. To sign edits, save, reopen, then sign.

Errors

do { _ = try await PdfDocument(data: data) }
catch PdfError.passwordRequired { /* prompt */ }
catch let error as PdfError { print(error.description) }

All platforms