Shows how to redact faces in documents with PSPDFProcessor and Apple’s CIDetector. Get additional resources by visiting our PDF redaction library for iOS.


//
// Copyright © 2020-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
final class FaceRedactionExample: Example {
// MARK: Lifecycle
override init() {
super.init()
title = "Face Redaction"
contentDescription = "Shows how to redact faces in documents with PSPDFProcessor."
category = .documentProcessing
priority = 12
}
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController {
let document = AssetLoader.document(for: "Flight Attendants.pdf")
// If there are any existing redact annotations, remove them.
for pageIndex in 0..<document.pageCount {
let existingRedactAnnotations = document.annotationsForPage(at: pageIndex, type: .redaction)
document.remove(annotations: existingRedactAnnotations)
}
let status = StatusHUDItem.indeterminateProgress(withText: "Detecting faces...")
status.setHUDStyle(.black)
status.push(animated: true, on: delegate.currentViewController?.view.window, completion: nil)
// Prepare a face detector. As this is an expensive operation, try to reuse this instance as much as possible.
#if targetEnvironment(simulator)
// `CIDetectors` don't work with high accuracy in simulators for some reason.
// A similar problem is described here https://developer.apple.com/forums/thread/722685,
// but there's no solution aside from forcing the low accuracy on simulators for now
let options = [CIDetectorAccuracy: CIDetectorAccuracyLow]
#else
let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
#endif
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options)!
// Perform this expensive work on a background queue.
DispatchQueue.global(qos: .background).async {
var documentRedactionAnnotations: [RedactionAnnotation] = []
for pageIndex in 0..<document.pageCount {
// Render this page. For efficiency, we can work with a scaled page render and still get good results.
let scaleFactor: CGFloat = 1 / 3.0
let pageSize = document.pageInfoForPage(at: pageIndex)!.size
let scaledPageSize = CGSize(width: pageSize.width * scaleFactor, height: pageSize.height * scaleFactor)
let renderedPage = try! document.imageForPage(at: pageIndex, size: scaledPageSize, clippedTo: .zero, annotations: nil, options: nil)
// Detect faces on the rendered page.
let ciImage = CIImage(cgImage: renderedPage.cgImage!)
let transform = CGAffineTransform(scaleX: 1 / (scaleFactor * renderedPage.scale), y: 1 / (scaleFactor * renderedPage.scale))
let faces = faceDetector.features(in: ciImage, options: nil)
// Place a redaction annotation on top of each detected face.
let redactionAnnotations = faces.map { face -> RedactionAnnotation in
let faceBounds = face.bounds.applying(transform)
let redaction = RedactionAnnotation()
redaction.boundingBox = faceBounds
redaction.rects = [faceBounds]
redaction.color = .orange
redaction.fillColor = .black
redaction.outlineColor = .green
redaction.pageIndex = pageIndex
return redaction
}
documentRedactionAnnotations.append(contentsOf: redactionAnnotations)
}
// Add the detected redaction annotations on the main queue and hide the progress window.
DispatchQueue.main.async {
document.add(annotations: documentRedactionAnnotations)
let statusDone = StatusHUDItem.success(withText: "Done")
statusDone.pushAndPop(withDelay: 1, animated: true, on: delegate.currentViewController?.view.window)
status.pop(animated: true)
}
}
return FaceRedactionPDFViewController(document: document)
}
final class FaceRedactionPDFViewController: PDFViewController {
override func commonInit(with document: Document?, configuration: PDFConfiguration) {
super.commonInit(with: document, configuration: configuration)
let redactButton = UIBarButtonItem(title: "Redact", style: .plain, target: self, action: #selector(applyRedactions))
navigationItem.setRightBarButtonItems([annotationButtonItem, activityButtonItem, outlineButtonItem, redactButton], for: .document, animated: false)
}
@objc func applyRedactions() {
let processorConfiguration = Processor.Configuration(document: document)!
processorConfiguration.applyRedactions()
let redactedDocumentURL = FileHelper.temporaryPDFFileURL(prefix: "redacted")
let processor = Processor(configuration: processorConfiguration, securityOptions: nil)
try! processor.write(toFileURL: redactedDocumentURL)
self.document = Document(url: redactedDocumentURL)
}
}
}

This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.