Customizing the Annotation Toolbar on iOS

The AnnotationToolbar(opens in a new tab) in PSPDFKit was designed to be flexible and highly configurable. It’s based on the FlexibleToolbar(opens in a new tab), which is a subclass of UIView and not UIToolbar. Early versions of PSPDFKit used a toolbar-based version, but it turned out to be too inflexible.

By default, the annotation toolbar can either dock to the top or anchor on the left/right side of the PDFViewController’s view.

This is fully configurable via setting FlexibleToolbar.Position and setting either supportedToolbarPositions(opens in a new tab) or toolbarPosition(opens in a new tab) on the toolbar:

  • .inTopBar
  • .left
  • .right
  • .top
  • .horizontal
  • .vertical
  • .default
  • .all

If FlexibleToolbar.Position.top is a supported toolbar position, you must disable the document label by setting documentLabelEnabled(opens in a new tab) to AdaptiveConditional.NO in your PDFConfiguration(opens in a new tab). Otherwise, an assertion will occur.

Presentation

The annotation toolbar can be shown or hidden using the annotationButtonItem(opens in a new tab) defined on PDFViewController. This bar button item is already part of the default rightBarButtonItems on NavigationItem(opens in a new tab). If you like, you can of course customize its placement to your liking.

If you want to invoke the annotation toolbar programmatically, you have two options.

You can invoke the annotationButtonItem by using action dispatching:

let annotationButtonItem = pdfController.annotationButtonItem
let action = annotationButtonItem.action!
UIApplication.shared.sendAction(action, to: annotationButtonItem.target, from: nil, for: nil)

Or you can toggle the toolbar manually:

// If not in document view mode, it'll be weird.
pdfController.setViewMode(.document, animated: true)
pdfController.annotationToolbarController?.updateHostView(nil, container: nil, viewController: pdfController)
UsernameHelper.ask(forDefaultAnnotationUsernameIfNeeded: pdfController, completionBlock: { _ in
pdfController.annotationToolbarController?.toggleToolbar(animated: true)
})

Asking for the user’s author name is an optional (but recommended) step. This way, you’ll ensure the newly created annotations are associated with the correct author name.

Toolbar Buttons

Annotation Buttons

The annotation toolbar utilizes button grouping order to efficiently display a large amount of annotation tools. The toolbar comes preconfigured with default annotation groups for both iPad and iPhone, but you can also set your own groups by assigning new groups, which is done by creating an AnnotationToolConfiguration(opens in a new tab) object.

Toolbar groups are defined as an array of AnnotationToolConfiguration.ToolGroup(opens in a new tab) objects, which themselves contain AnnotationToolConfiguration.ToolItem(opens in a new tab) instances:

let configuration = AnnotationToolConfiguration(annotationGroups: [
AnnotationToolConfiguration.ToolGroup(items: [
AnnotationToolConfiguration.ToolItem(type: .ink, variant: .inkPen, configurationBlock: AnnotationToolConfiguration.ToolItem.inkConfigurationBlock())
]),
AnnotationToolConfiguration.ToolGroup(items: [
AnnotationToolConfiguration.ToolItem(type: .line),
AnnotationToolConfiguration.ToolItem(type: .polyLine)
])
])

Finally, to set the AnnotationToolConfiguration(opens in a new tab) in your PDFViewController, you can use this code after creating the PDFViewController:

controller.annotationToolbarController?.annotationToolbar.configurations = [configuration]

To get the default button icons for tools with variants, pass one of the following as the configurationBlock argument of the ToolItem initializer:

To customize the button icon, return a UIImage containing your custom icon from the configurationBlock(opens in a new tab). Whenever possible, try to return a template image from the configuration block (UIImageRenderingModeAlwaysTemplate(opens in a new tab)):

let configurationBlock = { (item: AnnotationToolConfiguration.ToolItem, container: AnyObject?, tintColor: UIColor) -> UIImage in
let image = UIImage(named: "Custom Button Icon")!
return image.withRenderingMode(.alwaysTemplate)
}

Use the provided tint color only when you need multi-color images.

You can disable annotation toolbar configurations by setting the configuration property to nil. In such a case, the toolbar will show a list of all editableAnnotationTypes(opens in a new tab) without any grouping.

Standard Buttons

In addition to the annotation group buttons, the toolbar also provides some additional buttons to manage toolbar presentation, undo/redo actions, and access the style manager. The buttons are automatically added or omitted, depending on the toolbar and PSPDFKit configuration settings. These buttons can be customized by overriding the doneButton, undoButton, redoButton, and strokeColorButton properties. The buttons can also be completely removed by returning nil from the overridden getters.

To override the properties, you’ll have to subclass AnnotationToolbar(opens in a new tab). Read the overriding classes guide for more information.

Custom Buttons

The annotation toolbar also provides a convenient hook to add additional non-annotation type-specific buttons to the toolbar. You add these buttons by assigning an array of ToolbarButton(opens in a new tab) items to the additionalButtons(opens in a new tab) property on the annotation toolbar. The buttons will be positioned in between the annotation buttons and the undo/redo buttons.

Button Overflow

The toolbar usually auto-sizes to accommodate all of its buttons. If this cannot be achieved due lack of available view real estate, the toolbar automatically clips buttons flagged with the collapsible flag (from ToolbarButton(opens in a new tab)) and groups them in a special collapsedButtons item.

Auto Sizing

In vertical mode, the annotation toolbar will automatically size its height depending on the available screen real estate, the available toolbar configurations, and the active standard toolbar buttons. The final toolbar height is determined by first querying FlexibleToolbarContainerDelegate.flexibleToolbarContainerContentRect(_:for:)(opens in a new tab), a method PDFViewController implements and one you could override if you have custom elements that the toolbar should avoid. The toolbar then checks the required sizing constraints of all registered toolbar configurations and related standard buttons, trying to find the configuration that best fits the rect that flexibleToolbarContainerContentRect(_:for:) returned. If toolbar configurations are disabled (configurations == nil), the toolbar will auto-size to fit as many annotation types from editableAnnotationTypes(opens in a new tab) as possible.

The buttons property of the annotation toolbar is set only after toolbar sizing completes. This is required, because the toolbar size is a prerequisite for determining which buttons will actually be shown on the toolbar. If you modify the buttons property of the annotation toolbar manually, auto-sizing might no longer yield acceptable results. In that case, you’ll have to override preferredSizeFitting(_:for:)(opens in a new tab) and manually adjust the sizing.

Appearance Customization

The annotation toolbar exposes a variety of staying hooks for either direct or UIAppearance-based customization. See the appearance styling(opens in a new tab) guide for additional information.

Setting the toolbarDelegate of the FlexibleToolbar

The toolbarDelegate(opens in a new tab) of the FlexibleToolbar(opens in a new tab) isn’t used by PSPDFKit and can be set freely.

The easiest way to do this is to access the annotation toolbar via the annotation toolbar controller:

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.annotationToolbar.toolbarDelegate = toolbarDelegate

You can also create a subclass of AnnotationToolbarController(opens in a new tab) and override annotationToolbar(opens in a new tab) to set it there, or create a subclass of AnnotationToolbar(opens in a new tab) and override init(annotationStateManager:)(opens in a new tab) to set it.

Showing and Hiding the Annotation Toolbar

You can show and hide the annotation toolbar with AnnotationToolbarController(opens in a new tab).

First you have to set the hostView(opens in a new tab) with FlexibleToolbarController.updateHostView(_:container:viewController:)(opens in a new tab):

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.updateHostView(nil, container: nil, viewController: pdfController)

After that, you can show the toolbar with FlexibleToolbarController.showToolbar(animated:)(opens in a new tab):

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.showToolbar(animated: true)

To hide the toolbar, use FlexibleToolbarController.hideToolbar(animated:)(opens in a new tab):

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.hideToolbar(animated: true)