Highlight Text in PDF using Swift for iOS

Shows how to automatically create a highlight annotation after selecting text, replicating the behavior of Apple Books app. Get additional resources by visiting our guide on programmatically creating PDF annotations 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 PSPDFKit
import PSPDFKitUI
class BooksHighlightingExample: Example {
override init() {
super.init()
title = "Create Highlights From Selected Text"
contentDescription = "Shows how to automatically create a highlight annotation after selecting text."
category = .controllerCustomization
priority = 25
}
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController? {
CustomPDFViewController(document: AssetLoader.writableDocument(for: .psychologyResearch, overrideIfExists: true))
}
}
private class CustomPDFViewController: PDFViewController, UIGestureRecognizerDelegate {
override func commonInit(with document: Document?, configuration: PDFConfiguration) {
super.commonInit(with: document, configuration: configuration.configurationUpdated { builder in
// Disable the menu that allows to create annotations.
builder.isCreateAnnotationMenuEnabled = false
})
// Remove annotationButtonItem since we only want highlight annotations,
// and these are created without going into special mode.
navigationItem.setRightBarButtonItems([thumbnailsButtonItem, activityButtonItem, outlineButtonItem, searchButtonItem], for: .document, animated: false)
// Enable the highlighting mode for Apple Pencil.
annotationStateManager.state = .highlight
annotationStateManager.stylusMode = .stylus
}
override func viewDidLoad() {
super.viewDidLoad()
// Add a custom gesture recognizer so that we can independently track
// when a touch begins and ends.
let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognizerDidChangeState))
recognizer.minimumPressDuration = 0
recognizer.delegate = self
view.addGestureRecognizer(recognizer)
}
// A flag that tells whether we're tracking a fresh text selection or not.
private var isFreshSelection = false
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Let our gesture recognizer work "in background" and not interfere
// with any other gesture recognizer.
true
}
@objc private func longPressGestureRecognizerDidChangeState(_ recognizer: UILongPressGestureRecognizer) {
// Don't process touches outside of page views.
guard let pageView = documentViewController?.visiblePageView(at: recognizer.location(in: documentViewController?.view)) else {
return
}
// Track when a new text selection begins.
if recognizer.state == .began {
isFreshSelection = pageView.selectionView.selectedGlyphs.isEmpty
return
}
// Make sure that we're tracking a fresh text selection.
if recognizer.state == .ended, isFreshSelection, !pageView.selectionView.selectedGlyphs.isEmpty {
// Create a highlight annotation.
let highlight = HighlightAnnotation.textOverlayAnnotation(with: pageView.selectionView.selectedGlyphs)!
highlight.pageIndex = pageView.pageIndex
document?.add(annotations: [highlight])
// Wait until touch processing completes in the event loop cycle.
// Modifying selection directly in this method, while Nutrient is
// still processing touches internally, will lead to bad behavior.
DispatchQueue.main.async {
pageView.select(annotations: [highlight], presentMenu: true, animated: true)
}
}
}
}

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