Compare PDF Documents using Swift for iOS
Compare documents by using a different stroke color for each of two document and creating a new documend combining the two. Get additional resources by visiting our guide on comparing PDF documents in iOS.
//// Copyright © 2021-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
class ComparisonExample: IndustryExample {
override init() { super.init() title = "Document Comparison" contentDescription = "Shows how Nutrient can be used to compare PDF documents and highlight changes." extendedDescription = "Quickly compare, highlight, and identify PDF changes. Use manual document alignment to get a precise comparison." url = URL(string: "https://www.nutrient.io/guides/ios/compare-documents/")! category = .componentsExamples priority = 3 }
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController { ComparisonViewController(example: self) }
}
private class ComparisonViewController: PDFTabbedViewController, DocumentAlignmentViewControllerDelegate, PDFTabbedViewControllerDelegate {
/// Initialize the receiver with the reference to the parent example. init(example: IndustryExample) { super.init(pdfViewController: nil) moreInfo = MoreInfoCoordinator(with: example, presentationContext: self) }
@available(*, unavailable) required init?(coder decoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func commonInit(withPDFController somePdfController: PDFViewController?) { super.commonInit(withPDFController: somePdfController) // Closing tabs is unsupported in this example. closeMode = .disabled // Disabling the ability to hide user interface also makes the whole // documents fit under the navigation bar. pdfController.updateConfiguration { $0.userInterfaceViewMode = .always $0.shouldShowRedactionInfoButton = false } // The old and new documents should always be visible. insertDocument(oldDocument, at: 0, makeVisible: false, animated: false) insertDocument(newDocument, at: 1, makeVisible: false, animated: false) // Generate the initial (misaligned) comparison document. let processor = ComparisonProcessor(configuration: .default()) comparisonDocument = try! processor.comparisonDocument(oldDocument: oldDocument, newDocument: newDocument) // We want to update the "Align..." button visibility when selected tab // changes. For that we need to implement a delegate method. delegate = self }
override func viewDidLoad() { super.viewDidLoad() navigationItem.leftItemsSupplementBackButton = true pdfController.navigationItem.title = "Comparison" pdfController.navigationItem.leftBarButtonItems = [moreInfo.barButton] pdfController.navigationItem.rightBarButtonItems = [] // Manually add the "Align..." button to the user interface view. // `PDFTabbedViewController` uses a single `PDFViewController` for all // tabs, so you only need to add it once and then manage its visibility. alignButton.translatesAutoresizingMaskIntoConstraints = false pdfController.userInterfaceView.addSubview(alignButton) NSLayoutConstraint.activate([ alignButton.trailingAnchor.constraint(equalTo: pdfController.userInterfaceView.safeAreaLayoutGuide.trailingAnchor, constant: -16), alignButton.bottomAnchor.constraint(equalTo: pdfController.userInterfaceView.safeAreaLayoutGuide.bottomAnchor, constant: -10), ]) // We want to update the "Align..." button visibility immediately. updateAlignButtonVisibility() }
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) moreInfo.showAlertIfNeeded() }
// MARK: Documents
/// The old version of the document. private lazy var oldDocument: Document = AssetLoader.document(for: "Floor Plan A.pdf")
/// The new version of the document. private lazy var newDocument: Document = AssetLoader.document(for: "Floor Plan B.pdf")
/// The comparison document. private var comparisonDocument: Document? { didSet { if let oldValue { removeDocument(oldValue, animated: false) } if let newValue = comparisonDocument { insertDocument(newValue, at: 2, makeVisible: true, animated: false) } } }
// MARK: User Interface
private var moreInfo: MoreInfoCoordinator!
private func presentDocumentAlignmentViewController() { // Pass the two documents to be aligned. let viewController = DocumentAlignmentViewController(oldDocument: oldDocument, newDocument: newDocument, configuration: .default()) // In addition to using the delegate methods, you can also use the // `comparisonDocument` future to set up Combine bindings. viewController.delegate = self // Add an "x" button to the document alignment view controller so that // it can be used to cancel the alignment. viewController.navigationItem.leftBarButtonItems = [closeButtonItem] // Wrap the document alignment view controller in a navigation // controller so that it has a navigation bar. let navigationController = UINavigationController(rootViewController: viewController) navigationController.isModalInPresentation = true // UIKit will use `.pageSheet` modal presentation mode by default. You // can give the document alignment view controller more space by setting // `modalPresentationStyle` for `navigationController` to `.fullScreen`. present(navigationController, animated: true) }
private lazy var alignButton: UIButton = { let button = DocumentAlignmentButton() button.addTarget(self, action: #selector(alignButtonPressed), for: .touchUpInside) return button }()
@objc private func alignButtonPressed(_ sender: UIButton) { let alert = UIAlertController( title: "Manual Alignment", message: """ Select 3 points on both documents for manual alignment. For best \ results, choose points near corners of the document, and make sure \ to choose the points in the same order on both documents. """, preferredStyle: .alert ) alert.addAction(UIAlertAction( title: "Continue", style: .cancel, handler: { [self] _ in presentDocumentAlignmentViewController() } )) present(alert, animated: true) }
private func updateAlignButtonVisibility() { // Show the "Align..." button only in the tab containing the comparison // document. alignButton.isHidden = visibleDocument !== comparisonDocument }
private lazy var closeButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(closeButtonItemPressed))
@objc private func closeButtonItemPressed(_ sender: UIBarButtonItem) { dismiss(animated: true) }
// MARK: DocumentAlignmentViewControllerDelegate
func documentAlignmentViewController(_ sender: DocumentAlignmentViewController, didFinishWithComparisonDocument document: Document) { comparisonDocument = document dismiss(animated: true) }
func documentAlignmentViewController(_ sender: DocumentAlignmentViewController, didFailWithError error: Error) { dismiss(animated: true) { [self] in let alert = UIAlertController(title: "Couldn’t Create the Comparison Document", message: error.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel)) present(alert, animated: true) } }
// MARK: PDFTabbedViewControllerDelegate
func tabbedPDFController(_ tabbedPDFController: PDFTabbedViewController, didChangeVisibleDocument oldVisibleDocument: Document?) { updateAlignButtonVisibility() }
}
This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.