Customizing Menus on iOS
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 PDFViewControllerDelegate
to customize this behavior:
-
pdfViewController(_:shouldShow:atSuggestedTargetRect:forSelectedText:in:on:)
-
pdfViewController(_:shouldShow:atSuggestedTargetRect:forSelectedImage:in:on:)
-
pdfViewController(_:shouldShow:atSuggestedTargetRect:for:in:on:)
These calls will provide a menuItems
array of MenuItem
objects. These are like UIMenuItem
but allow block-based calls and images. You can return instances of either MenuItem
or UIMenuItem
, and you can return nil
to block any of these menus from appearing.
⚠️ Warning:
UIMenuController
is a shared object andPDFViewController
uses it extensively. Manually setting properties likemenuItems
on it will not work, as logic in the controller will most likely override it before it is presented. Instead, use the delegates to customizeUIMenuController
.
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 PDFConfiguration
in editableAnnotationTypes
. By default, this includes all annotation types.
To customize this, use the pdfViewController(_:shouldShow:atSuggestedTargetRect:forSelectedText:in:on:)
delegate. You can also disable certain types of menu actions via the allowedMenuActions
property.
API | Use |
---|---|
.search |
Allow search from selected text. |
.define |
Offers to show “Define” on selected text. Not available for Mac Catalyst (FB6823989: Feedback filed to offer UIReferenceLibraryViewController ). |
.wikipedia |
Offers a toggle for Wikipedia. |
.speak |
Allows text-to-speech. |
.share |
Allows sharing content. Also used for image selection. |
.copy |
Allows copying the selected text. |
.markup |
Allows marking up the selected text (highlight/underline/strikeout). |
.redact |
Allows redacting the selected text. |
.createLink |
Allows creating a link from the selected text. |
.annotationCreation |
Helper that encapsulates all annotation creation menu action types. |
.all |
All text selection entries. |
The menu delegate for annotations is pdfViewController(_:shouldShow:atSuggestedTargetRect:for:in:on:)
. 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 rather than maintaining a list of items you want to keep:
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, forSelectedText selectedText: String, in textRect: CGRect, on pageView: PDFPageView) -> [MenuItem] { // Filter out the "Copy" menu item. return menuItems.filter() {$0.identifier != TextMenu.copy.rawValue} }
- (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]; }]]; }
Make sure to filter using properties that are not localization dependent. The identifier
property of the MenuItem
object is not localized and is therefore a good choice:
public func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, forSelectedText selectedText: String, in textRect: CGRect, on pageView: PDFPageView) -> [MenuItem] { // 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 `TextSelectionMenuAction `). var newMenuItems = menuItems.filter { $0.identifier != TextMenu.wikipedia.rawValue } 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 = MenuItem(title: NSLocalizedString("Google", comment: ""), block: { // Create browser. let browser = WebViewController(url: queryURL) browser.delegate = pdfController browser.modalPresentationStyle = .popover browser.preferredContentSize = CGSize(width: 600, height: 500) let presentationOptions = [ .sourceRect: NSValue(cgRect: textRect), .inNavigationController: true, .closeButton: true ] as [PresentationOption : Any] pdfController.present(browser, options: presentationOptions, animated: true, sender: nil, completion: nil) }, identifier: "Google") newMenuItems.append(googleItem) return newMenuItems }
- (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 `UIMenuItem`s 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<PSPDFPresentationOption, id> *presentationOptions = @{ PSPDFPresentationOptionSourceRect: @(textRect), PSPDFPresentationOptionInNavigationController: @YES, PSPDFPresentationOptionCloseButton: @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.
Additional Menu Item Customizations
You can customize a MenuItem
object so that it does not close the menu automatically on tapping. Custom menu items can be hot-swapped when they are tapped, depending on the current state of the app, without the UIMenuController
disappearing in between.
For example, suppose you want to add a Play button to the menu, having it change to Pause when it is tapped and back to Play when tapped again. And suppose you also require the menu to remain open while this is happening. To accomplish this, you can use any of the shouldShowMenuItems
delegate methods and add your custom MenuItem
like this:
// Enum to keep track of the current state. enum PlayerState { case playing case paused } func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, forSelectedText selectedText: String, in textRect: CGRect, on pageView: PDFPageView) -> [MenuItem] { let playItem = MenuItem(title: "Play", block: { // Update the state and anything else that this button was actually supposed to do. self.state = .playing // Fetch the menu items again for the newly updated state. UIMenuController.shared.menuItems = self.pdfViewController(pdfController, shouldShow: menuItems, atSuggestedTargetRect: rect, forSelectedText: selectedText, in: textRect, on: pageView) // This presents the menu again after tapping on the custom item. UIMenuController.shared.hideMenu(from: pageView) UIMenuController.shared.showMenu(from: pageView, rect: rect) }, identifier: "Play") let pauseItem = MenuItem(title: "Pause", block: { // Update the state and anything else that this button was actually supposed to do. self.state = .paused // Fetch the menu items again for the newly updated state. UIMenuController.shared.menuItems = self.pdfViewController(pdfController, shouldShow: menuItems, atSuggestedTargetRect: rect, forSelectedText: selectedText, in: textRect, on: pageView) // This presents the menu again after tapping on the custom item. UIMenuController.shared.hideMenu(from: pageView) UIMenuController.shared.showMenu(from: pageView, rect: rect) }, identifier: "Pause") // Depending on the current state, add the play/pause item at the beginning of the menu items array. var newMenuItems = menuItems if self.state == .paused { newMenuItems = [playItem] + newMenuItems } else if self.state == .playing { newMenuItems = [pauseItem] + newMenuItems } return newMenuItems }
// Enum to keep track of the current state. typedef NS_ENUM(NSInteger, PlayerState) { PlayerStatePlaying, PlayerStatePaused }; - (NSArray<PSPDFMenuItem *> *)pdfViewController:(PSPDFViewController *)pdfController shouldShowMenuItems:(NSArray<PSPDFMenuItem *> *)menuItems atSuggestedTargetRect:(CGRect)rect forSelectedText:(NSString *)selectedText inRect:(CGRect)textRect onPageView:(PSPDFPageView *)pageView { PSPDFMenuItem *playItem = [[PSPDFMenuItem alloc] initWithTitle:@"Play" block:^{ // Update the state and anything else that this button was actually supposed to do. self.state = PlayerStatePlaying; // Fetch the menu items again for the newly updated state. UIMenuController.sharedMenuController.menuItems = [self pdfViewController:pdfController shouldShowMenuItems:menuItems atSuggestedTargetRect:rect forSelectedText:selectedText inRect:textRect onPageView:pageView]; // This presents the menu again after tapping on the custom item. [UIMenuController.sharedMenuController hideMenuFromView:pageView]; [UIMenuController.sharedMenuController showMenuFromView:pageView rect:rect]; } identifier:@"Play"]; PSPDFMenuItem *pauseItem = [[PSPDFMenuItem alloc] initWithTitle:@"Pause" block:^{ // Update the state and anything else that this button was actually supposed to do. self.state = PlayerStatePaused; // Fetch the menu items again for the newly updated state. UIMenuController.sharedMenuController.menuItems = [self pdfViewController:pdfController shouldShowMenuItems:menuItems atSuggestedTargetRect:rect forSelectedText:selectedText inRect:textRect onPageView:pageView]; // This presents the menu again after tapping on the custom item. [UIMenuController.sharedMenuController hideMenuFromView:pageView]; [UIMenuController.sharedMenuController showMenuFromView:pageView rect:rect]; } identifier:@"Pause"]; // Depending on the current state, add the play/pause item at the beginning of the menu items array. NSMutableArray *newMenuItems = [menuItems mutableCopy]; if (self.state == PlayerStatePaused) { [newMenuItems insertObject:playItem atIndex:0]; } else if (self.state == PlayerStatePlaying) { [newMenuItems insertObject:pauseItem atIndex:0]; } return newMenuItems; }
Creating Annotations

When long tapping on empty space, we will call the pdfViewController(_:shouldShow:atSuggestedTargetRect:for:in:on:)
variant without any annotations — this is to show the menu for creating annotations. There’s a convenience property called createAnnotationMenuEnabled
, which can be used to disable this feature in PDFConfiguration
, and you can also customize what types should be displayed there using createAnnotationMenuGroups
:
let controller = PDFViewController(document: document, configuration: PDFConfiguration { builder in builder.isCreateAnnotationMenuEnabled = false builder.createAnnotationMenuGroups = [ AnnotationToolConfiguration.ToolGroup(items: [ AnnotationToolConfiguration.ToolItem(type: .freeText), AnnotationToolConfiguration.ToolItem(type: .signature), AnnotationToolConfiguration.ToolItem(type: .note) ]), AnnotationToolConfiguration.ToolGroup(items: [ AnnotationToolConfiguration.ToolItem(type: .ink, variant: .inkPen, configurationBlock: AnnotationToolConfiguration.ToolItem.inkConfigurationBlock()) ]), AnnotationToolConfiguration.ToolGroup(items: [ AnnotationToolConfiguration.ToolItem(type: .ink, variant: .inkHighlighter, configurationBlock: AnnotationToolConfiguration.ToolItem.inkConfigurationBlock()) ]), AnnotationToolConfiguration.ToolGroup(items: [ AnnotationToolConfiguration.ToolItem(type: .image), AnnotationToolConfiguration.ToolItem(type: .stamp), AnnotationToolConfiguration.ToolItem(type: .sound) ]), AnnotationToolConfiguration.ToolGroup(items: [ AnnotationToolConfiguration.ToolItem(type: .eraser) ]) ] })
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:PSPDFAnnotationVariantStringInkPen configurationBlock:[PSPDFAnnotationGroupItem inkConfigurationBlock]] ]], [PSPDFAnnotationGroup groupWithItems:@[ [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringInk variant:PSPDFAnnotationVariantStringInkHighlighter configurationBlock:[PSPDFAnnotationGroupItem inkConfigurationBlock]] ]], [PSPDFAnnotationGroup groupWithItems:@[ [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringImage], [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringStamp], [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringSound] ]], [PSPDFAnnotationGroup groupWithItems:@[ [PSPDFAnnotationGroupItem itemWithType:PSPDFAnnotationStringEraser] ]] ]; }]];