Customize the Annotations List in Swift for iOS

Customize how the cells used in AnnotationTableViewController are drawn. Get additional resources by visiting our guide on customizing lists of 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 AnnotationListExample: Example {
override init() {
super.init()
title = "Customizing the Annotation List"
contentDescription = "Customize the annotation cell in AnnotationTableViewController."
category = .viewCustomization
priority = 70
}
override func invoke(with delegate: ExampleRunnerDelegate) -> UIViewController? {
let guide = AssetLoader.document(for: .welcome)
let controller = PDFViewController(document: guide) { builder in
builder.overrideClass(AnnotationCell.self, with: CustomAnnotationCell.self)
builder.overrideClass(AnnotationTableViewController.self, with: AnnotationListController.self)
}
controller.documentInfoCoordinator.availableControllerOptions = [.annotations]
// Simulate tapping on the outline button.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
controller.documentInfoCoordinator.present(
to: controller,
options: nil,
sender: controller.outlineButtonItem,
animated: true,
completion: nil
)
}
return controller
}
}
private class CustomAnnotationCell: AnnotationCell {
var isShared: Bool {
didSet {
sharingStateLabel.text = isShared ? "Shared" : "Not Shared"
// Ensure the size and placement are up to date
sharingStateLabel.sizeToFit()
setNeedsLayout()
}
}
// Note: This isn’t the greatest UI. A smaller sharing indicator would be nicer.
private var sharingStateLabel: UILabel
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
isShared = false
sharingStateLabel = UILabel()
sharingStateLabel.textColor = .psc_secondaryLabel
super.init(style: style, reuseIdentifier: reuseIdentifier)
accessoryView = sharingStateLabel
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented and isn’t needed")
}
}
private class AnnotationListController: AnnotationTableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let anyCell = super.tableView(tableView, cellForRowAt: indexPath)
guard let cell = anyCell as? CustomAnnotationCell else {
return anyCell
}
// Our superclass sets the annotation on the cell, so we know we may force unwrap.
let annotation = cell.annotation!
cell.isShared = sharedState(for: annotation)
return cell
}
private var sharedStateByAnnotationID: [String: Bool] = [:]
private func sharedState(for annotation: Annotation) -> Bool {
sharedStateByAnnotationID[annotation.id] ?? false
}
private func toggleSharedState(for annotation: Annotation) -> Bool {
let desiredState = !sharedState(for: annotation)
sharedStateByAnnotationID[annotation.id] = desiredState
return desiredState
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
guard let cell = tableView.cellForRow(at: indexPath) as? CustomAnnotationCell else {
return super.tableView(tableView, trailingSwipeActionsConfigurationForRowAt: indexPath)
}
let action = UIContextualAction(
style: .normal,
title: cell.isShared ? "Unshare" : "Share"
) { [weak cell, weak self] _, _, completion in
guard let cell, let self else {
return completion(false)
}
cell.isShared = self.sharedState(for: cell.annotation!)
completion(true)
}
return UISwipeActionsConfiguration(actions: [action])
}
}

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