Customizing the Annotation Toolbar on iOS

The AnnotationToolbar in PSPDFKit was designed to be flexible and highly configurable. It’s based on the FlexibleToolbar, 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 or toolbarPosition on the toolbar:

  • .inTopBar

  • .left

  • .right

  • .top

  • .horizontal

  • .vertical

  • .default

  • .all

Warning

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

Presentation

The annotation toolbar can be shown or hidden using the annotationButtonItem defined on PDFViewController. This bar button item is already part of the default rightBarButtonItems on NavigationItem. 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)
UIBarButtonItem *annotationButtonItem = pdfController.annotationButtonItem;
[UIApplication.sharedApplication sendAction:annotationButtonItem.action to:annotationButtonItem.target from:nil forEvent: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)
})
// If not in document view mode, it'll be weird.
[pdfController setViewMode:PSPDFViewModeDocument animated:YES];
[pdfController.annotationToolbarController updateHostView:nil container:nil viewController:pdfController];
[PSPDFUsernameHelper askForDefaultAnnotationUsernameIfNeeded:pdfController completionBlock:^(NSString *userName) {
    [pdfController.annotationToolbarController toggleToolbarAnimated:YES];
}];

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 object.

Toolbar groups are defined as an array of AnnotationToolConfiguration.ToolGroup objects, which themselves contain AnnotationToolConfiguration.ToolItem 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)
    ])
])
PSPDFAnnotationToolbarConfiguration *configuration = [[PSPDFAnnotationToolbarConfiguration alloc] initWithAnnotationGroups:@[
    [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringInk variant:PSPDFAnnotationVariantStringInkPen configurationBlock:[PSPDFAnnotationGroupItem inkConfigurationBlock]]
    ]],
    [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringLine],
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringPolyLine]
    ]]
]];

Finally, to set the AnnotationToolConfiguration in your PDFViewController, you can use this code after creating the PDFViewController:

controller.annotationToolbarController?.annotationToolbar.configurations = [configuration]
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. Whenever possible, try to return a template image from the configuration block (UIImageRenderingModeAlwaysTemplate):

let configurationBlock = { (item: AnnotationToolConfiguration.ToolItem, container: AnyObject?, tintColor: UIColor) -> UIImage in
    let image = UIImage(named: "Custom Button Icon")!
    return image.withRenderingMode(.alwaysTemplate)
}
PSPDFAnnotationGroupItemConfigurationBlock configurationBlock = ^UIImage *(PSPDFAnnotationGroupItem *item, id container, UIColor *tintColor) {
    UIImage *image = [UIImage imageNamed:@"Custom Button Icon"];
    return [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
};

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 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. Please 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 items to the additionalButtons 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) 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:), 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 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:) 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 guide for additional information.

Setting the toolbarDelegate of the FlexibleToolbar

The toolbarDelegate of the FlexibleToolbar 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
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];
pdfController.annotationToolbarController.annotationToolbar.toolbarDelegate = toolbarDelegate;

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

Showing and Hiding the Annotation Toolbar

You can show and hide the annotation toolbar with AnnotationToolbarController.

First you have to set the hostView with FlexibleToolbarController.updateHostView(_:container:viewController:):

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.updateHostView(nil, container: nil, viewController: pdfController)
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];
[pdfController.annotationToolbarController updateHostView:nil container:nil viewController:pdfController];

After that, you can show the toolbar with FlexibleToolbarController.showToolbar(animated:):

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.showToolbar(animated: true)
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];
[pdfController.annotationToolbarController showToolbarAnimated:YES];

To hide the toolbar, use FlexibleToolbarController.hideToolbar(animated:):

let pdfController = PDFViewController(document: document)
pdfController.annotationToolbarController?.hideToolbar(animated: true)
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];
[pdfController.annotationToolbarController hideToolbarAnimated:YES];