---
title: "Adding custom views to a page"
canonical_url: "https://www.nutrient.io/guides/ios/customizing-pdf-pages/adding-custom-views-to-a-page/"
md_url: "https://www.nutrient.io/guides/ios/customizing-pdf-pages/adding-custom-views-to-a-page.md"
last_updated: "2026-05-23T00:08:18.119Z"
description: "Learn how to enhance your Nutrient annotations by adding custom views and UI elements selectively for improved user interaction and display."
---

Some behaviors or appearance customizations can only be achieved by adding custom views to a page. If, for example, you want to customize interactions with a certain annotation type, or you want to display additional UI elements next to certain annotations, or you want to place auxiliary UI elements at certain positions of pages that satisfy particular criteria, adding custom views may be your best option to achieve this.

This guide covers the case where you want to customize the interactions with annotations of a certain type. If this approach doesn’t work for you, (see the section on [caveats](https://www.nutrient.io/guides/ios/customizing-pdf-pages/adding-custom-views-to-a-page.md#caveats)). Or, if you want to learn how to place auxiliary content on a page, look at the companion guide, [adding auxiliary or decorative views](https://www.nutrient.io/guides/ios/customizing-pdf-pages/adding-auxiliary-or-decorative-views.md).

## Use case: Selectively adding UI elements

If you want all annotations of a certain type to display additional UI elements, you can use subclasses of `AnnotationView` for this purpose. By default, Nutrient renders most annotations as part of the page, but it creates an overlay view when an annotation is selected. `LinkAnnotation`, `NoteAnnotation`, `SoundAnnotation`, and `FileAnnotation` are always displayed in overlay mode.

The first step to using a custom view class in overlay mode is to identify what to subclass. If the annotation has drawn content like, for example, a stamp, a good starting point is `HostingAnnotationView` and its subclasses. If the annotation is more of a placeholder for content, `LinkAnnotationBaseView` and its subclasses might be more appropriate.

For this example, let’s say we want a view that adds an information button below _some kinds of_ stamps. We don’t ship a more specific view class for stamps, so we use `HostingAnnotationView` as our starting point:

### SWIFT

```swift

class InfoDisplayingStampView: HostingAnnotationView {
    private let infoButton: UIButton
    override required init(frame: CGRect) {
        infoButton = UIButton(type:.infoLight)
        infoButton.translatesAutoresizingMaskIntoConstraints = false

        super.init(frame: frame)

        infoButton.isHidden = false
        infoButton.add(self, action: #selector(showImageInfo(_:)), for:.touchUpInside)

        addSubview(infoButton)
        NSLayoutConstraint.activate([
            leadingAnchor.constraint(equalTo: infoButton.leadingAnchor),
            bottomAnchor.constraint(equalTo: infoButton.bottomAnchor)
        ])
    }

    override var annotation: Annotation? {
        willSet {
            infoButton.isHidden = newValue!= nil
        }
    }

    @objc
    func showInfo(_ sender: Any?) {
        // Not useful, but logging does show some info…
        print("Showing \(String(describing: annotation))")
    }
}

```

### OBJECTIVE-C

```objc

@interface PSCInfoDisplayingStampView : PSPDFHostingAnnotationView @end

@implementation PSCInfoDisplayingStampView {
    UIButton *_infoButton;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        _infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
        _infoButton.hidden = YES;
        [_infoButton addTarget:self action:@selector(showInfo:) forControlEvent:UIControlEventTouchUpInside];

        [self addSubview:_infoButton];
        [NSLayoutContstraint activateConstraints:@[
            [self.leadingAnchor constraintEqualToAnchor:_infoButton.leadingAnchor],
            [self.bottomAnchor constraintEqualToAnchor:_infoButton.bottomAnchor],
        ]];
    }

    return self;
}

- (void)setAnnotation:(PSPDFAnnotation *)annotation {
    [super setAnnotation:annotation];
    _infoButton.hidden = [(PSPDFStampAnnotation *)annotation image]!= nil;
}

- (void)showInfo:(id)sender {
    // Not useful, but logging does show some info…
    NSLog(@"Showing %@", self.annotation);

}

@end

```

We don’t want this class to behave drastically different, but we want to have a secondary action that can only be triggered when tapping a smaller portion of the stamp. The information button we added here gives us just that, and the `showInfo(_:)` action acts as a stand-in for any actual functionality we want to trigger.

Now that we have a custom view class, we need to make sure we can actually use it. Since we said we only want to show the information button on _some kinds of_ stamps instead of _all stamps_, we can’t rely on type-based overrides for this customization. Instead, we need to decide whether we want our custom view class by inspecting the actual annotation instances.

When a `PageView` needs to instantiate a new annotation view, it asks `AnnotationManager` for the class it should use. This is where we can dock in: Whenever a page view needs a new view for a stamp, we’ll return our custom `HostingAnnotationView` subclass, and we’ll extend `StampAnnotation` with a computed property to tell us if the stamp has additional information:

### SWIFT

```swift

class ViewCustomizingAnnotationManager: AnnotationManager {
    override func annotationView(for annotation: Annotation) -> AnyClass? {
        guard
            let stamp = annotation as? StampAnnotation,
            stamp.hasAdditionalMetadata
        else {
            return super.annotationView(for: annotation)
        }

        return InfoDisplayingStampView.self
    }
}

extension StampAnnotation {
    var hasAdditionalMetadata: Bool {
        // Replace with a useful predicate in your own code…
        objectNumber % 2 == 0
    }
}

```

### OBJECTIVE-C

```objc

@interface PSPDFStampAnnotation (PSCHasAdditionalMetadata)

@property (nonatomic, readonly) BOOL psc_hasAdditionalMetadata;

@end

@implementation PSPDFStampAnnotation (PSCHasAdditionalMetadata)

- (BOOL)psc_hasAdditionalMetadata {
    // Replace with a useful predicate in your own code…
    return self.objectNumber % 2 == 0;
}

@end

@interface PSCViewCustomizingAnnotationManager : PSPDFAnnotationManager @end

@implementation PSCViewCustomizingAnnotationManager

- (Class)annotationViewClassForAnnotation:(PSPDFAnnotation *)annotation {
    if ([annotation isKindOfClass:PSPDFStampAnnotation.class] &&
        [(PSPDFStampAnnotation *)annotation psc_hasAdditionalMetadata]) {
        return PSCInfoDisplayingStampView.class;
    }

    return [super annotationViewClassForAnnotation:annotation];
}

@end

```

There’s at least one more step we need to take, though: By default, `Document` will create instances of `AnnotationManager` — not our `ViewCustomizingAnnotationManager`.

To change this, we have to register a class override with the `Document` first. For more in-depth information on how to achieve this, [refer to our subclassing guide](https://www.nutrient.io/guides/ios/getting-started/overriding-classes.md). Once that’s set up, all stamp annotations returning `true` from `hasAdditionalMetadata` will display an information button when they’re selected.

If you also want to use your custom view class when the annotation isn’t selected, you’ll need to tell Nutrient that every stamp annotation where the computed property returns `true` should be displayed in overlay mode. The simplest way to achieve that is by also replacing the built-in `StampAnnotation` class and overriding its `isOverlay` getter:

### SWIFT

```swift

class EagerlyOverlayingStampAnnotation: StampAnnotation {
    override var isOverlay: Bool {
        get { super.isOverlay?? hasAdditionalMetadata }
        set { super.isOverlay = newValue }
    }
}

```

### OBJECTIVE-C

```objc

@interface PSCEagerlyOverlayingStampAnnotation : PSPDFStampAnnotation @end

@implementation PSCEagerlyOverlayingStampAnnotation

- (BOOL)isOverlay {
    return [super isOverlay]?: self.psc_hasAdditionalMetadata;
}

@end

```

### Caveats

Customizing the display of `TextMarkupAnnotation` subclasses in this way isn’t supported. In addition to the `rects` of the annotation, strikethroughs, underlines, and squiggly lines all need information about the baseline offset within these `rects` for drawing — information which isn’t readily available. To make matters worse, the marked-up text isn’t limited to being set horizontally or vertically on the page. Instead, it can flow at arbitrary angles, even varying within the same annotation, which can’t be expressed through the `rects` property.

For these annotation types, consider working with [auxiliary views](https://www.nutrient.io/guides/ios/customizing-pdf-pages/adding-auxiliary-or-decorative-views.md) instead.
---

## Related pages

- [Document Features](/guides/ios/features/document-features.md)
- [Adding Auxiliary Or Decorative Views](/guides/ios/customizing-pdf-pages/adding-auxiliary-or-decorative-views.md)
- [Options to disable or enable PDF editing on iOS](/guides/ios/features/controlling-pdf-editing.md)
- [Configure document sharing options on iOS](/guides/ios/miscellaneous/document-sharing.md)
- [Document Downloads](/guides/ios/miscellaneous/document-downloads.md)
- [SwiftUI PDF library](/guides/ios/getting-started/swiftui.md)

