Customize Vertical Annotation Toolbar in Swift for iOS
Create a completely custom annotation toolbar. Get additional resources by visiting our guide on annotation state manager 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 CustomVerticalAnnotationToolbarExample: Example { override init() { super.init() title = "Custom Vertical Always-Visible Annotation Toolbar" contentDescription = "Reimplements a completely custom annotation toolbar" category = .viewCustomization priority = 30 }
override func invoke(with delegate: ExampleRunnerDelegate?) -> UIViewController? { let document = AssetLoader.document(for: .annualReport) let controller = VerticalAnnotationToolbarPDFViewController(document: document) { // Remove the long-press annotation menu $0.createAnnotationMenuGroups = [] $0.editableAnnotationTypes = [.ink, .freeText] } // Remove the default annotation bar button item if let items = controller.navigationItem.rightBarButtonItems(for: .document)? .filter({ $0 != controller.annotationButtonItem }) { controller.navigationItem.setRightBarButtonItems(items, for: .document, animated: false) } return controller }}
final private class VerticalAnnotationToolbarPDFViewController: PDFViewController { var verticalToolbar: CustomVerticalAnnotationToolbar?
override func viewDidLoad() { super.viewDidLoad()
// Create the custom toolbar and anchor it on the trailing side of the PDF controller view. let verticalToolbar = CustomVerticalAnnotationToolbar(annotationStateManager: annotationStateManager) verticalToolbar.translatesAutoresizingMaskIntoConstraints = false view.addSubview(verticalToolbar)
NSLayoutConstraint.activate([ verticalToolbar.centerYAnchor.constraint(equalTo: view.centerYAnchor), verticalToolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) self.verticalToolbar = verticalToolbar }
override func setViewMode(_ viewMode: ViewMode, animated: Bool) { super.setViewMode(viewMode, animated: animated) // Ensure custom annotation toolbar is hidden when thumbnails are shown UIView.animate(withDuration: 0.25, delay: 0, options: .allowUserInteraction) { self.verticalToolbar?.alpha = viewMode == .thumbnails ? 0 : 1 } }}
final private class CustomVerticalAnnotationToolbar: UIView { let annotationStateManager: AnnotationStateManager var drawButton: UIButton? var freeTextButton: UIButton? var undoButton: UIButton? var redoButton: UIButton?
required init(annotationStateManager: AnnotationStateManager) { self.annotationStateManager = annotationStateManager super.init(frame: .zero)
annotationStateManager.add(self)
let editableAnnotationTypes = annotationStateManager.pdfController?.configuration.editableAnnotationTypes ?? [] if editableAnnotationTypes.contains(.ink) { drawButton = button(withImageName: "ink", action: #selector(inkButtonPressed)) } if editableAnnotationTypes.contains(.freeText) { freeTextButton = button(withImageName: "freetext", action: #selector(freeTextButtonPressed)) } undoButton = button(withImageName: "undo", action: #selector(undoButtonPressed)) undoButton?.isEnabled = false redoButton = button(withImageName: "redo", action: #selector(redoButtonPressed)) redoButton?.isEnabled = false
let stackView = UIStackView(arrangedSubviews: [drawButton, freeTextButton, undoButton, redoButton].compactMap({ $0 })) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical stackView.distribution = .fillEqually addSubview(stackView)
let buttonLength: CGFloat = 44
NSLayoutConstraint.activate([ stackView.widthAnchor.constraint(equalToConstant: buttonLength), stackView.heightAnchor.constraint(equalToConstant: buttonLength * CGFloat(stackView.arrangedSubviews.count)), widthAnchor.constraint(equalTo: stackView.widthAnchor), heightAnchor.constraint(equalTo: stackView.heightAnchor) ])
backgroundColor = UIColor.psc_systemBackground }
func button(withImageName imageName: String, action: Selector) -> UIButton { let button = UIButton(type: .custom) let image = SDK.imageNamed(imageName)!.withRenderingMode(.alwaysTemplate) button.setImage(image, for: .normal) button.addTarget(self, action: action, for: .touchUpInside) button.translatesAutoresizingMaskIntoConstraints = false return button }
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
@objc func inkButtonPressed() { annotationStateManager.toggleState(.ink) }
@objc func freeTextButtonPressed() { annotationStateManager.toggleState(.freeText) }
@objc func undoButtonPressed() { undoManager?.undo() }
@objc func redoButtonPressed() { undoManager?.redo() }}
extension CustomVerticalAnnotationToolbar: AnnotationStateManagerDelegate { static let selectedColor = UIColor.psc_label.withAlphaComponent(0.2) static let deselectedColor = UIColor.clear
func annotationStateManager(_ manager: AnnotationStateManager, didChangeState oldState: Annotation.Tool?, to newState: Annotation.Tool?, variant oldVariant: Annotation.Variant?, to newVariant: Annotation.Variant?) { drawButton?.backgroundColor = newState == .ink ? CustomVerticalAnnotationToolbar.selectedColor : CustomVerticalAnnotationToolbar.deselectedColor freeTextButton?.backgroundColor = newState == .freeText ? CustomVerticalAnnotationToolbar.selectedColor : CustomVerticalAnnotationToolbar.deselectedColor }
func annotationStateManager(_ manager: AnnotationStateManager, didChangeUndoState undoEnabled: Bool, redoState redoEnabled: Bool) { undoButton?.isEnabled = undoEnabled redoButton?.isEnabled = redoEnabled }}
This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.