Review and Reply to Annotations on iOS

Annotation replies enable you and other users to have written discussions directly inside a document. As of PSPDFKit 7.5 for iOS, PSPDFKit provides convenient APIs for accessing replies in a document, as well as user interface (UI) components for viewing and editing replies.

Annotations may also have author-specific states associated with them, which allows users to review a proposed change to, for example, say whether they agree or disagree with the change.

PSPDFKit has built-in support to let users view, search, and add replies and reviews. There’s also a model-level API for programmatic access so that you can build custom features. PSPDFKit implements annotation replies and state conforming to the PDF specification and is fully compatible with the same features in Adobe Acrobat Reader.

Terminology

This is the terminology we use:

  • Reply — An annotation with an in-reply-to annotation set.

  • Text reply — A reply where the author-specific state is unspecified.

  • State reply/state annotation — A reply where the author-specific state is specified.

  • Review — A state reply where the author-specific state is set to a review state.

  • Comments — The list containing an annotation and all of its text replies. This means the first comment displays the contents of the parent annotation. This is mostly a user-facing term.

Licensing

Replies is a separate component in your PSPDFKit license. Without this component included in your license, your app won’t be able to view, search, or add annotation replies. Please contact our Sales team to add this component to your license.

Third-Party Compatibility

Replies were introduced in the PDF 1.6 specification and are fully compatible with Adobe Acrobat. Other PDF viewers might not be able to show all replies or simply fail to display status information.

Screenshot of comments in Adobe Acrobat

Annotation Replies aren’t supported in Instant JSON.

User Interface

Users can tap an annotation and then tap Comments in the menu. This will present a NoteAnnotationViewController, which shows all the comments on the annotation, as well as any reviews on those comments. Users can both add and edit the comments and add reviews.

Screenshot of comments

Just like with other annotations, users can edit comments even if they didn’t create them.

If there are replies to replies, PSPDFKit will flatten the nested conversation into a single list sorted by date.

Disabling the Replies and Reviews UI

If your license includes annotation replies but you want to disable letting the user view and add replies and reviews, you can define a document features source and disable the replies and reviews feature:

class MyFeaturesSource: NSObject, PDFDocumentFeaturesSource {

    var features: PDFDocumentFeatures?

    var canShowAnnotationReplies: Bool {
        return false
    }
}
@interface MyFeaturesSource : NSObject <PSPDFDocumentFeaturesSource>
@end
@implementation MyFeaturesSource
@synthesize features = _features;
- (BOOL)canShowAnnotationReplies {
    return NO;
}
@end

Add your features source to the document:

let document: Document = ...
document.features.add([MyFeaturesSource()])
PSPDFDocument *document = ...
[document.features addSources:@[[[MyFeaturesSource alloc] init]]];

Model API

Replies to an annotation are themselves annotations. A reply is linked to its parent by means of the inReplyToAnnotation property. In the PDF, this is modeled with the IRT entry in the annotation dictionary. There’s no direct connection in the reverse direction, i.e. between an annotation and its replies. Instead, all the annotations on the same page must be searched. However, if you use the UI provided by PSPDFKit, this is all taken care of.

The author-specific state associated with an annotation isn’t specified in the annotation itself, but rather in a separate text annotation that refers to the original annotation by means of the inReplyToAnnotation property. Each time a user changes the state, a new note annotation is created and appended to the linked list of state changes. To find the complete set of states associated with an annotation, it’s necessary to repeatedly search all the annotations on the same page to follow the reverse of the inReplyToAnnotation property. Again, this is already handled in PSPDFKit’s UI.

Since replies are annotations, they’re included when querying the annotations on a page. You can check if an annotation is a reply using the isReply property. Doing this doesn’t require the Replies component in the license.

If you’ve licensed the Replies component, you can both read and set the annotation a reply is associated with using the inReplyToAnnotation property. To add a text reply:

guard let document = originalAnnotation.document else { return }

let textReply = NoteAnnotation(contents: "text of the comment")

// Replies must be on the same page as their `inReplyToAnnotation` property.
// For multiprovider documents, PSPDFKit will fix up the page index when the annotation is added to the document.
textReply.pageIndex = originalAnnotation.absolutePageIndex

// `originalAnnotation` must have already been added to the document.
textReply.inReplyTo = originalAnnotation

document.add(annotations: [textReply], options: nil)
if (originalAnnotation.document == nil) {
    return
}

PSPDFNoteAnnotation *textReply = [[PSPDFNoteAnnotation alloc] initWithContents:@"text of the comment"];

// Replies must be on the same page as their `inReplyToAnnotation` property.
// For multiprovider documents, PSPDFKit will fix up the page index when the annotation is added to the document.
textReply.pageIndex = originalAnnotation.absolutePageIndex;

// originalAnnotation must have already been added to the document.
textReply.inReplyToAnnotation = originalAnnotation;

[originalAnnotation.document addAnnotations:@[textReply] options:nil];

You can read and set the author-specific state with the authorState property on NoteAnnotation. If the value of this property is .unspecified, then the annotation isn’t in a state of reply.

For Objective-C, you’ll have to use the authorStateModel property, along with authorState. If the properties are PSPDFAnnotationAuthorStateModelUnspecified and PSPDFAnnotationAuthorStateUnspecified, then the annotation isn’t a state reply. These properties need to be kept consistent. To add a state reply, do the following:

guard let document = originalAnnotation.document else { return }

let stateReply = NoteAnnotation()

stateReply.authorState = .reviewing(.completed)

// Replies must be on the same page as their `inReplyToAnnotation` property.
// For multiprovider documents, PSPDFKit will fix up the page index when the annotation is added to the document.
stateReply.pageIndex = originalAnnotation.absolutePageIndex

// `originalAnnotation` must have already been added to the document.
stateReply.inReplyTo = originalAnnotation

document.add(annotations: [stateReply], options: nil)
if (originalAnnotation.document == nil) {
    return
}

PSPDFNoteAnnotation *stateReply = [[PSPDFNoteAnnotation alloc] init];

stateReply.authorStateModel = PSPDFAnnotationAuthorStateModelReview;
stateReply.authorState = PSPDFAnnotationAuthorStateAccepted;

// Replies must be on the same page as their `inReplyToAnnotation` property.
// For multiprovider documents, PSPDFKit will fix up the page index when the annotation is added to the document.
stateReply.pageIndex = originalAnnotation.absolutePageIndex;

// `originalAnnotation` must have already been added to the document.
stateReply.inReplyToAnnotation = originalAnnotation;

[originalAnnotation.document addAnnotations:@[stateReply] options:nil];