Nutrient 12 migration guide

This guide covers updating an iOS or Mac Catalyst project from Nutrient iOS SDK 11.5 to Nutrient iOS SDK 12. We encourage you to update as soon as possible, in order to take advantage of future new features and fixes.

Nutrient iOS SDK 12 fully supports iOS 14, 15, and 16. Xcode 14 or later is required to use this version of the SDK. Learn more in our version support guide.

Information

To determine if you need to take action, check the list of the deprecated APIs. If you use a deprecated API in your project, take appropriate action.

New menu system

Nutrient iOS SDK 12 introduces a new, modern menu system for annotation creation and annotation selection menus. It uses the UIMenu-based API to display either a horizontal bar or a context menu, depending on platform and input type.

Unlike the now-deprecated UIMenuItem-based API, the modern menu system relies on declaring the entire menu tree upfront, and it takes care of displaying proper submenus when needed. Nutrient iOS SDK 12 adds a new suite of customizations to take advantage of this approach.

Information

The modern menu system is only available for annotation creation and annotation selection menus. Text and image selection menus will adopt the modern menu system in a future version of Nutrient iOS SDK.

Customizing the menus directly

PDFViewController now offers two new delegate methods for customizing the annotation creation and annotation selection menus directly:

The example below appends a custom action to the annotation selection menu:

func pdfViewController(_ sender: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    let customAction = UIAction(title: "Custom") { _ in
        print("Hello from custom action!")
    }
    return suggestedMenu.replacingChildren(suggestedMenu.children + [customAction])
}

You can use UIAction to insert closure-based actions, UICommand to insert responder chain actions, and UIMenu to insert submenus. UIDeferredMenuElement is also supported.

As a result, the following delegate methods and subclassing hooks that were used to customize the annotation creation and annotation selection menus have been deprecated and will be removed in a future version of Nutrient iOS SDK:

  • PDFViewControllerDelegate.pdfViewController(_:shouldShow:atSuggestedTargetRect:for:in:on:)

  • PDFPageView.menuItemsForNewAnnotation(at:)

  • PDFPageView.menuItems(for:)

  • PDFPageView.shouldMoveStyleMenuEntriesIntoSubmenu

Customizing choices in the style menu

The new annotationMenuConfiguration property allows you to customize the available choices for some of the properties in the Style menu for selected annotations by setting a custom closure for one of the following properties:

The example below customizes the available line width choices for ink annotations on the first page, but it leaves the default choices for all other annotations on all other pages:

let configuration = PDFConfiguration {
    $0.annotationMenuConfiguration = AnnotationMenuConfiguration {
        $0.lineWidthChoices = { annotation, pageView, defaultChoices in
            if annotation is InkAnnotation, pageView.pageIndex == 0 {
                return [2, 5, 10, 20]
            } else {
                return defaultChoices
            }
        }
    }
}

The following PDFPageView subclassing hooks that were used to customize these choices have been deprecated and will be removed in a future version of Nutrient iOS SDK:

  • PDFPageView.colorMenuItems(for:)

  • PDFPageView.fillColorMenuItems(for:)

  • PDFPageView.opacityMenuItem(for:with:)

  • PDFPageView.defaultColorOptions(for:)

  • PDFPageView.availableFontSizes

  • PDFPageView.availableLineWidths

Backward compatibility considerations

The new UIMenu-based delegate methods are incompatible with the deprecated UIMenuItem-based delegate methods and PDFPageView subclassing hooks, and they must not be mixed.

Because the new customizations mark a significant departure from the now-deprecated UIMenuItem-based API, Nutrient iOS SDK 12 will ease the transition by choosing to stick with the legacy menu system under certain conditions.

I’m not interested in customizing menus.

If you’re not interested in customizing menus and you don’t use any of the new or deprecated customizations, the modern menu system will be used by default.

I want to customize menus and I want to adopt the modern menu system.

If you implement either of the new UIMenu-based delegate methods to customize the annotation creation or annotation selection menu, this will be treated as an explicit opt in to the modern menu system for that specific menu, even if you still have one of the deprecated delegate methods or subclassing hooks in your code.

I’ve been customizing menus but I’m not ready to implement the new UIMenu-based API yet.

If you don’t implement any of the new UIMenu-based delegate methods but you do have one of the deprecated delegate methods or subclassing hooks in your code, Nutrient will stick with the legacy menu system and respect the deprecated customizations.

If this is the case, Nutrient will help you identify which exact deprecated customization caused it to opt out of the modern menu system by logging a warning similar to the following:

Presenting the legacy menu for configuration (...) because the following deprecated customizations are implemented: 'MyDelegate.pdfViewController(_:shouldShow:atSuggestedTargetRect:for:in:on:)', 'MyPageView.defaultColorOptions(for:)'. Remove them or implement 'MyDelegate.pdfViewController(_:menuForAnnotations:onPageView:appearance:suggestedMenu:)' to explicitly opt into the modern menu system.
Warning

This is a temporary mechanism designed to ease the transition to the modern menu system. The legacy menu system is limited and may exhibit problems. The deprecated APIs will eventually be removed, and the legacy menu system will cease to exist. We strongly encourage you to adopt the modern menu system as soon as possible. If you need help, don’t hesitate to reach out to us on support.

Use cases

This section explores the most common use cases when customizing the annotation creation and annotation selection menus, and it describes how to achieve the same results using the new UIMenu-based API.

Presenting the annotation creation menu

Use the tryToShowAnnotationMenu(at:in:) method to programmatically present the menu that normally appears when you long press on space without selectable text or annotations:

// Before this update.
pageView.showAnnotationMenu(at: point, animated: true)
// After this update.
viewController.interactions.tryToShowAnnotationMenu(at: point, in: coordinateSpace)

Presenting the menu for selected annotations

Use the new select(annotations:presentMenu:animated:) method to select annotations and set true for the presentMenu parameter. It’s no longer possible to present the menu for annotations without selecting them at the same time:

// Before this update.
let targetRect = pageView.convert(annotation.boundingBox, from: pageView.pdfCoordinateSpace)
pageView.showMenu(for: [annotation], targetRect: targetRect, option: .menuOnly, animated: true)
// After this update.
pageView.select(annotations: [annotation], presentMenu: true, animated: true)

Customizing the annotation creation menu

Use the new pdfViewController(_:menuForCreatingAnnotationAt:onPageView:appearance:suggestedMenu:) delegate method to customize the menu that appears when you long press on space without selectable text or annotations:

// Before this update.
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, for annotations: [Annotation]?, in annotationRect: CGRect, on pageView: PDFPageView) -> [MenuItem] {
    if annotations == nil {
        // Return the customized `menuItems`.
    } else {
        return menuItems
    }
}
// After this update.
func pdfViewController(_ sender: PDFViewController, menuForCreatingAnnotationAt point: CGPoint, onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    // Return the customized `suggestedMenu`.
}

To disable the annotation creation menu entirely, either return a UIMenu with no children from the above delegate method, or set the isCreateAnnotationMenuEnabled configuration property to false.

Customizing the menu for selected annotations

Use the new pdfViewController(_:menuForAnnotations:onPageView:appearance:suggestedMenu:) delegate method to customize the menu that appears when you select one or more annotations:

// Before this update.
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, for annotations: [Annotation]?, in annotationRect: CGRect, on pageView: PDFPageView) -> [MenuItem] {
    if let annotations, !annotations.isEmpty {
        // Return the customized `menuItems`.
    } else {
        return menuItems
    }
}
// After this update.
func pdfViewController(_ sender: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    // Return the customized `suggestedMenu`.
}

To disable the annotation selection menu entirely, return a UIMenu with no children from the above delegate method.

Filtering suggested menu elements

Use one of the new UIMenu-based delegate methods and modify the suggestedMenu parameter to exclude certain actions or submenus. Keep in mind that you need to search the entire menu tree, and not just the children of the root menu:

// Before this update.
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, for annotations: [Annotation]?, in annotationRect: CGRect, on pageView: PDFPageView) -> [MenuItem] {
    menuItems.filter {
        $0.identifier == TextMenu.annotationMenuCopy.rawValue || $0.identifier == TextMenu.annotationMenuRemove.rawValue
    }
}
// After this update.
func pdfViewController(_ sender: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    suggestedMenu.filterActions(anyOf: [.PSPDFKit.copy, .PSPDFKit.delete])
}
Information

In the above example, the filterActions(anyOf:) function isn’t part of the public API, but you can view its source in our Catalog example project.

Inserting custom menu elements

Use one of the new UIMenu-based delegate methods and modify the suggestedMenu parameter to insert a menu element at any index:

// Before this update.
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, for annotations: [Annotation]?, in annotationRect: CGRect, on pageView: PDFPageView) -> [MenuItem] {
    let customMenuItem = MenuItem(title: "Custom") {
        print("Hello from custom menu item!")
    }
    return [customMenuItem] + menuItems
}
// After this update.
func pdfViewController(_ sender: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    let customAction = UIAction(title: "Custom") { _ in
        print("Hello from custom action!")
    }
    return suggestedMenu.replacingChildren([customAction] + suggestedMenu.children)
}

Presenting submenus

Use one of the new UIMenu-based delegate methods and modify the suggestedMenu parameter to include a UIMenu among the children. Nutrient will take care of the rest; there’s no longer a need to manually present submenus:

// Before this update.
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, for annotations: [Annotation]?, in annotationRect: CGRect, on pageView: PDFPageView) -> [MenuItem] {
    let nestedMenuItem = MenuItem(title: "Nested") {
        print("Hello from nested menu item!")
    }
    let submenuMenuItem = MenuItem(title: "Submenu") {
        UIMenuController.shared.menuItems = [nestedMenuItem]
        UIMenuController.shared.showMenu(from: pageView, rect: rect)
    }
    return [submenuMenuItem] + menuItems
}
// After this update.
func pdfViewController(_ sender: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    let nestedAction = UIAction(title: "Nested") { _ in
        print("Hello from nested action!")
    }
    let submenu = UIMenu(title: "Submenu", children: [
        nestedAction
    ])
    return suggestedMenu.replacingChildren([submenu] + suggestedMenu.children)
}

Displaying menu elements as images

Menu elements will be displayed as images if their titles are empty. This works with all supported menu elements — UIAction, UICommand, and UIMenu:

// Before this update.
func pdfViewController(_ pdfController: PDFViewController, shouldShow menuItems: [MenuItem], atSuggestedTargetRect rect: CGRect, for annotations: [Annotation]?, in annotationRect: CGRect, on pageView: PDFPageView) -> [MenuItem] {
    guard let annotations, !annotations.isEmpty else {
        return menuItems
    }
    let lockMenuItem = MenuItem(title: "Lock", image: UIImage(systemName: "lock")) {
        annotations.forEach { $0.flags.insert(.locked) }
    }
    return [lockMenuItem] + menuItems
}
// After this update.
func pdfViewController(_ sender: PDFViewController, menuForAnnotations annotations: [Annotation], onPageView pageView: PDFPageView, appearance: EditMenuAppearance, suggestedMenu: UIMenu) -> UIMenu {
    let lockImage = UIImage(systemName: "lock")!
    lockImage.accessibilityLabel = "Lock"
    let lockAction = UIAction(title: appearance == .contextMenu ? "Lock" : "", image: lockActionImage) { _ in
        annotations.forEach { $0.flags.insert(.locked) }
    }
    return [lockAction] + menuItems
}

The above example demonstrates how you can combine various conditions to decide whether a menu element should be displayed as an image or not. We recommend always including the title when the menu appears as a .contextMenu.

Information

Always set the accessibilityLabel for menu element images that don’t have titles. This will ensure they’re properly discoverable with VoiceOver.

Further reading

For more information about the modern menu system, check out our customizing menus on iOS guide and the API documentation for PDFViewControllerDelegate, PDFPageView, and AnnotationMenuConfiguration.

Deprecated APIs

This is the list of all symbols that were deprecated in Nutrient iOS SDK 12. If you use, implement, or override any of the following, take appropriate action.

Miscellaneous

  • UnitTo.point
    Use a different unit for real-world distances instead.

Legacy menu system

Legacy menu item identifiers

  • TextMenu.annotationMenuAlignment
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuAlignmentCenter
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuAlignmentLeft
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuAlignmentRight
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuBlendMode
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuCancel
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuColor
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuCopy
    Use the .PSPDFKit.copy action identifier in the modern menu system.

  • TextMenu.annotationMenuCustomColor
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuEdit
    Use the .PSPDFKit.editFreeText or .PSPDFKit.editSound action identifiers in the modern menu system.

  • TextMenu.annotationMenuFillColor
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuFinishRecording
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuFitToText
    Use the .PSPDFKit.sizeToFit action identifier in the modern menu system.

  • TextMenu.annotationMenuFont
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuGroup
    Use the .PSPDFKit.group action identifier in the modern menu system.

  • TextMenu.annotationMenuHighlightType
    Use the .PSPDFKit.type menu identifier in the modern menu system.

  • TextMenu.annotationMenuInspector
    Use the .PSPDFKit.inspector action identifier in the modern menu system.

  • TextMenu.annotationMenuLineEnd
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineStart
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeButt
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeCircle
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeClosedArrow
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeDiamond
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeNone
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeOpenArrow
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeReverseClosedArrow
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeReverseOpenArrow
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeSlash
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuLineTypeSquare
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuMerge
    Use the .PSPDFKit.merge action identifier in the modern menu system.

  • TextMenu.annotationMenuNote
    Use the .PSPDFKit.comments action identifier in the modern menu system.

  • TextMenu.annotationMenuOpacity
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuPaste
    Use the .PSPDFKit.paste action identifier in the modern menu system.

  • TextMenu.annotationMenuPause
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuPauseRecording
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuPlay
    Use the .PSPDFKit.play action identifier in the modern menu system.

  • TextMenu.annotationMenuPreviewFile
    Use the .PSPDFKit.previewFile action identifier in the modern menu system.

  • TextMenu.annotationMenuRemove
    Use the .PSPDFKit.delete action identifier in the modern menu system.

  • TextMenu.annotationMenuSave
    Use the .PSPDFKit.store action identifier in the modern menu system.

  • TextMenu.annotationMenuSize
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuStyle
    Use the .PSPDFKit.style menu identifier in the modern menu system.

  • TextMenu.annotationMenuThickness
    This value is no longer used in the modern menu system.

  • TextMenu.annotationMenuUngroup
    Use the .PSPDFKit.ungroup action identifier in the modern menu system.