Customizing Menus

PSPDFKit uses UIMenuController for context-sensitive menus when you select text, images, or annotations or long press on an empty space. There are several delegates in PSPDFViewControllerDelegate to customize this behavior:

These calls will provide a menuItems array of PSPDFMenuItem objects. These are like UIMenuItem but allow block-based calls and images. You can return instances of either PSPDFMenuItem or UIMenuItem, and you can return nil to block any of these menus from appearing.

⚠️ Warning: UIMenuController is a shared object and PSPDFViewController uses it extensively. Manually setting properties like menuItems on it will not work, as logic in the controller will most likely override it before it is being presented. Instead, use the delegates to customize.

The image below shows how the menu for text selection usually appears.

The annotation types offered here for creation are based on the values of the currently active PSPDFConfiguration in editableAnnotationTypes. By default, this includes all annotation types.

To customize this, use the pdfViewController:shouldShowMenuItems:atSuggestedTargetRect:forSelectedText:inRect:onPageView: delegate. You can also disable certain types of menu actions via the allowedMenuActions property:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// These are the menu options when text is selected on this document.
public struct PSPDFTextSelectionMenuAction : OptionSet {
    public init(rawValue: UInt)

    /// Allow search from selected text.
    public static var search: PSPDFTextSelectionMenuAction { get }
    /// Offers to show the "Define" menu item on selected text.
    public static var define: PSPDFTextSelectionMenuAction { get }
    /// Offers a toggle for Wikipedia.
    public static var wikipedia: PSPDFTextSelectionMenuAction { get }
    /// Allows text-to-speech.
    public static var speak: PSPDFTextSelectionMenuAction { get }
    public static var all: PSPDFTextSelectionMenuAction { get }
}
Copy
1
2
3
4
5
6
7
8
/// These are the menu options when text is selected on this document.
typedef NS_OPTIONS(NSUInteger, PSPDFTextSelectionMenuAction) {
    PSPDFTextSelectionMenuActionSearch    = 1 << 0, /// Allow search from selected text.
    PSPDFTextSelectionMenuActionDefine    = 1 << 1, /// Offers to show the "Define" menu item on selected text.
    PSPDFTextSelectionMenuActionWikipedia = 1 << 2, /// Offers a toggle for Wikipedia.
    PSPDFTextSelectionMenuActionSpeak     = 1 << 3, /// Allows text-to-speech.
    PSPDFTextSelectionMenuActionAll       = NSUIntegerMax
};

The menu delegate for annotations is pdfViewController:shouldShowMenuItems:atSuggestedTargetRect:forAnnotations:inRect:onPageView:. If this is called with nil as the annotation argument, the menu to create new annotations will be shown (and in that case, annotationRect will also be nil).

Note that if you want to remove certain menu items, as a general rule, you should filter out the unwanted items in a blacklist:

Copy
1
2
3
4
func pdfViewController(_ pdfController: PSPDFViewController, shouldShow menuItems: [PSPDFMenuItem], atSuggestedTargetRect rect: CGRect, forSelectedText selectedText: String, in textRect: CGRect, on pageView: PSPDFPageView) -> [PSPDFMenuItem] {
    // Filter out the "Copy" menu item.
    return menuItems.filter() {$0.identifier != PSPDFTextMenu.copy.rawValue}
}
Copy
1
2
3
4
5
6
- (NSArray<PSPDFMenuItem *> *)pdfViewController:(PSPDFViewController *)pdfController shouldShowMenuItems:(NSArray<PSPDFMenuItem *> *)menuItems atSuggestedTargetRect:(CGRect)rect forAnnotations:(NSArray<PSPDFAnnotation *> *)annotations inRect:(CGRect)annotationRect onPageView:(PSPDFPageView *)pageView {
    // Filter out the "Copy" menu item.
    return [menuItems filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(PSPDFMenuItem *menuItem, NSDictionary *bindings) {
        return ![menuItem.identifier isEqualToString:PSPDFTextMenuCopy];
    }]];
}

If you try to whitelist menu items, you might break functionality. The identifier property of the PSPDFMenuItem object is not localized and is perfect for comparison:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public func pdfViewController(_ pdfController: PSPDFViewController, shouldShow menuItems: [PSPDFMenuItem], atSuggestedTargetRect rect: CGRect, forSelectedText selectedText: String, in textRect: CGRect, on pageView: PSPDFPageView) -> [PSPDFMenuItem] {
    // Disable Wikipedia.
    // Note that for words that are in the iOS dictionary instead of Wikipedia, we show the "Define" menu item with the native dictionary UI.
    // There is also a simpler way to disable Wikipedia (see `PSPDFTextSelectionMenuAction`).
    var newMenuItems = menuItems.filter { $0.identifier != PSPDFTextMenuWikipedia }

    guard let query = selectedText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
        let queryURL = URL(string: String(format: "https://www.google.com/search?q=%@", arguments: [query])) else {
            return newMenuItems
    }

    // Add the option to google for the currently selected text.
    let googleItem = PSPDFMenuItem(title: NSLocalizedString("Google", comment: ""), block: {
        // Create browser.
        let browser = PSPDFWebViewController(url: queryURL)
        browser.delegate = pdfController
        browser.modalPresentationStyle = .popover
        browser.preferredContentSize = CGSize(width: 600, height: 500)

        let presentationOptions: [String : Any] = [
            PSPDFPresentationRectKey: NSValue(cgRect: textRect),
            PSPDFPresentationInNavigationControllerKey: true,
            PSPDFPresentationCloseButtonKey: true
        ]

        pdfController.present(browser, options: presentationOptions, animated: true, sender: nil, completion: nil)
        }, identifier: "Google")

    newMenuItems.append(googleItem)
    return newMenuItems
}
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (NSArray<PSPDFMenuItem *> *)pdfViewController:(PSPDFViewController *)pdfController shouldShowMenuItems:(NSArray<PSPDFMenuItem *> *)menuItems atSuggestedTargetRect:(CGRect)rect forSelectedText:(NSString *)selectedText inRect:(CGRect)textRect onPageView:(PSPDFPageView *)pageView {

    // Disable Wikipedia.
    // Be sure to check for the `PSPDFMenuItem` class; there might also be classic `UIMenuItems` in the array.
    // Note that for words that are in the iOS dictionary instead of Wikipedia, we show the "Define" menu item with the native dictionary UI.
    // There is also a simpler way to disable Wikipedia (see `PSPDFTextSelectionMenuAction`).
    NSMutableArray *newMenuItems = [menuItems mutableCopy];
    for (PSPDFMenuItem *menuItem in menuItems) {
        if ([menuItem isKindOfClass:PSPDFMenuItem.class] && [menuItem.identifier isEqualToString:PSPDFTextMenuWikipedia]) {
            [newMenuItems removeObjectIdenticalTo:menuItem];
            break;
        }
    }

    // Add the option to google for the currently selected text.
    PSPDFMenuItem *googleItem = [[PSPDFMenuItem alloc] initWithTitle:NSLocalizedString(@"Google", nil) block:^{
        NSString *URLString = [NSString stringWithFormat:@"https://www.google.com/search?q=%@", [selectedText stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];

        // Create browser.
        PSPDFWebViewController *browser = [[PSPDFWebViewController alloc] initWithURL:(NSURL *)[NSURL URLWithString:URLString]];
        browser.delegate = pdfController;
        browser.modalPresentationStyle = UIModalPresentationPopover;
        browser.preferredContentSize = CGSizeMake(600.f, 500.f);

        NSDictionary *presentationOptions = @{
            PSPDFPresentationRectKey: BOXED(textRect),
            PSPDFPresentationInNavigationControllerKey: @YES,
            PSPDFPresentationCloseButtonKey: @YES
        };

        [pdfController presentViewController:browser options:presentationOptions animated:YES sender:nil completion:NULL];
    } identifier:@"Google"];
    [newMenuItems addObject:googleItem];

    return newMenuItems;
}

For more details, take a look at PSCSimpleAnnotationInspectorExample from our Catalog app.

Creating Annotations

When long tapping on empty space, we will call the forAnnotations: variant without any annotations — this is to show the create annotations menu. There’s a convenience property called createAnnotationMenuEnabled, which can be used to disable this feature in PSPDFConfiguration, and you can also customize what types should be displayed there using createAnnotationMenuGroups:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let controller = PSPDFViewController(document: document, configuration: PSPDFConfiguration { builder in
    builder.isCreateAnnotationMenuEnabled = false
    builder.createAnnotationMenuGroups = [
        PSPDFAnnotationGroup(items: [
            PSPDFAnnotationGroupItem(type: .freeText),
            PSPDFAnnotationGroupItem(type: .signature),
            PSPDFAnnotationGroupItem(type: .note)
        ]),
        PSPDFAnnotationGroup(items: [
            PSPDFAnnotationGroupItem(type: .ink, variant: .inkPen, configurationBlock: PSPDFAnnotationGroupItem.inkConfigurationBlock())
        ]),
        PSPDFAnnotationGroup(items: [
            PSPDFAnnotationGroupItem(type: .ink, variant: .inkHighlighter, configurationBlock: PSPDFAnnotationGroupItem.inkConfigurationBlock())
        ]),
        PSPDFAnnotationGroup(items: [
            PSPDFAnnotationGroupItem(type: .image),
            PSPDFAnnotationGroupItem(type: .stamp),
            PSPDFAnnotationGroupItem(type: .sound)
        ]),
        PSPDFAnnotationGroup(items: [
            PSPDFAnnotationGroupItem(type: .eraser)
        ])
    ]
})
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
PSPDFViewController *controller = [[PSPDFViewController alloc] initWithDocument:document configuration:[PSPDFConfiguration configurationWithBuilder:^(PSPDFConfigurationBuilder *builder) {
    builder.createAnnotationMenuEnabled = NO;
    builder.createAnnotationMenuGroups = @[
      [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringFreeText],
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringSignature],
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringNote]
      ]],
      [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringInk
          variant:PSPDFAnnotationStringInkPen
          configurationBlock:[PSPDFAnnotationGroupItem inkConfigurationBlock]]
      ]],
      [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringInk
          variant:PSPDFAnnotationStringInkHighlighter
          configurationBlock:[PSPDFAnnotationGroupItem inkConfigurationBlock]]
      ]],
      [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringImage],
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringStamp],
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringSound]
      ]],
      [PSPDFAnnotationGroup groupWithItems:@[
        [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringEraser]
      ]]
    ];
}]];