---
title: "PDF report generation on iOS | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/ios/generating-pdfs/generating-pdf-reports/"
md_url: "https://www.nutrient.io/guides/ios/generating-pdfs/generating-pdf-reports.md"
last_updated: "2026-06-08T09:14:14.409Z"
description: "Learn how to create PDF reports on iOS devices using Nutrient templates, even offline. Enhance business intelligence with professional document generation."
---

# Generate PDF reports on iOS

![Generate On-Device PDF Reports on iOS header](@/assets/images/blog/2018/generate-on-device-pdf-reports-on-ios/article-header.png)

With Nutrient, you can use templates to create a PDF report on a mobile device and without an internet connection. This is useful in business intelligence and can potentially amend applications such as Crystal Reports or SQL Server Reporting Services.

See also: [Generate on-device PDF reports on iOS](https://www.nutrient.io/blog/generate-on-device-pdf-reports-on-ios/) on our blog.

## Generating PDF documents

With a combination of features like the [Document Editor](https://www.nutrient.io/../../features/document-editor/#programmatic-access) and annotations, Nutrient can create professional reports based on a template and include custom cover and final pages. See the [`ReportPDFGenerationExample.swift`](https://github.com/PSPDFKit/pspdfkit-ios-catalog/blob/master/Catalog/Examples/DocumentGeneration/ReportPDFGenerationExample.swift) example from the [Catalog app](https://www.nutrient.io/guides/ios/getting-started/example-projects.md#nutrient-catalog) for more information.

### Setting up the pages

First, we set up the report’s pages. We do this with a template document from which we only preserve the first and last pages to use as the cover and final pages.

We then add a new empty page with a pattern, and to finish, we insert a page from another document. This is how it looks in code:

### SWIFT

```swift

let document =...
// Processor configuration needs a valid document.
let configuration = Processor.Configuration(document: document)!

// Keep only the first and last page of the original document.
configuration.removePages(IndexSet(1..<Int(document.pageCount - 1)))

// Add a new page with a pattern grid as the third page of the report.
let pageTemplate = PageTemplate(pageType:.tiledPatternPage, identifier:.grid5mm)
configuration.addNewPage(at: 2, configuration: PDFNewPageConfiguration(pageTemplate: pageTemplate, builderBlock: { builder in
    $0.backgroundColor =.white
}))

// Add a page from an existing document.
let anotherDocument =...
configuration.addNewPage(at: 3, configuration: PDFNewPageConfiguration(pageTemplate: PageTemplate(document: anotherDocument, sourcePageIndex: 7)))

// `PSPDFProcessor` doesn't modify the document, rather it creates an output file instead.
let generatedReportURL =...

do {
    let processor = Processor(configuration: configuration, securityOptions: nil)
    processor.delegate = self
    try processor.write(toFileURL: generatedReportURL)
} catch {
    print("Error while processing document: \(error)")
}

```

### OBJECTIVE-C

```objc

PSPDFDocument *document =...
PSPDFProcessorConfiguration *configuration = [[PSPDFProcessorConfiguration alloc] initWithDocument:document];

// Keep only the first and last page of the original document.
[configuration removePages:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, document.pageCount - 1)]];
PSPDFPageTemplate *pageTemplate = [[PSPDFPageTemplate alloc] initWithPageType:PSPDFNewPageTypeTiledPatternPage identifier:PSPDFTemplateIdentifierGrid5mm];
[configuration addNewPageAtIndex:2 configuration:[PSPDFNewPageConfiguration newPageConfigurationWithPageTemplate:pageTemplate builderBlock:^(PSPDFNewPageConfigurationBuilder *builder) {
    builder.backgroundColor = UIColor.whiteColor;
}]];

// Add a page from an existing document.
PSPDFDocument *anotherDocument =...
PSPDFPageTemplate *derivedPageTemplate = [[PSPDFPageTemplate alloc] initWithDocument:anotherDocument sourcePageIndex:7];
[configuration addNewPageAtIndex:3 configuration:[PSPDFNewPageConfiguration newPageConfigurationWithPageTemplate:derivedPageTemplate builderBlock:NULL]];

// `PSPDFProcessor` doesn't modify the document, rather it creates an output file instead.
NSURL *generatedReportURL =...
PSPDFProcessor *processor = [[PSPDFProcessor alloc] initWithConfiguration:configuration securityOptions:nil];
processor.delegate = self;
[processor writeToFileURL:generatedReportURL error:NULL];

```

### Adding text and images

To add text and images, we use free text and stamp annotations, like so:

### SWIFT

```swift

// Create a free text annotation.
let freeTextAnnotation = PSPDFFreeTextAnnotation()
freeTextAnnotation.contents = "Some Annotations"
freeTextAnnotation.boundingBox = CGRect(x: 228, y: 924, width: 600, height: 80)
freeTextAnnotation.fontSize = 40

// Add the free text annotation to the document.
document.add([freeTextAnnotation])

// Create an image stamp annotation.
let imageStampAnnotation = PSPDFStampAnnotation()
let image = UIImage(named: "exampleimage.jpg")!
imageStampAnnotation.image = image
imageStampAnnotation.boundingBox = CGRect(x: 60, y: 400, width: image.size.width / 4, height: image.size.height / 4)

// Add the image stamp annotation to the document.
document.add([imageStampAnnotation])

```

### OBJECTIVE-C

```objc

// Create a free text annotation.
PSPDFFreeTextAnnotation *freeTextAnnotation = [[PSPDFFreeTextAnnotation alloc] init];
freeTextAnnotation.contents = @"Some Annotations";
freeTextAnnotation.boundingBox = CGRectMake(228.f, 924.f, 600.f, 80.f);
freeTextAnnotation.fontSize = 40.f;

// Add the free text annotation to the document.
[document addAnnotations:@[imageStampAnnotation] options:nil]

// Create an image stamp annotation.
PSPDFStampAnnotation *imageStampAnnotation = [[PSPDFStampAnnotation alloc] init];
imageStampAnnotation.image = [UIImage imageNamed:@"exampleimage.jpg"];
imageStampAnnotation.boundingBox = CGRectMake(60.f, 400.f, imageStampAnnotation.image.size.width / 4.f, imageStampAnnotation.image.size.height / 4.f);

// Add the image stamp annotation to the document.
[document addAnnotations:@[imageStampAnnotation] options:nil];

```

See our [programmatically creating annotations](https://www.nutrient.io/../../annotations/programmatically-creating-annotations/) guide for more details.

### Adding PDF pages or content into existing PDF pages

Vector stamp annotations allow you to embed and freely place and scale PDF pages _into_ other PDF pages, like so:

### SWIFT

```swift

// Create the URL of the appearance stream that uses a PDF file.
let samplesURL = Bundle.main.resourceURL?.appendingPathComponent("Samples")
let logoURL = samplesURL?.appendingPathComponent("PSPDFKit Logo.pdf")

// Create the vector stamp annotation.
let vectorStampAnnotation = StampAnnotation()
vectorStampAnnotation.boundingBox = CGRect(x: 50, y: 724, width: 200, height: 200)
vectorStampAnnotation.appearanceStreamGenerator = FileAppearanceStreamGenerator(fileURL: logoURL)

// Add the vector stamp annotation to the document.
document.add(annotations: [vectorStampAnnotation])

```

### OBJECTIVE-C

```objc

// Create the URL of the appearance stream that uses a PDF file.
NSURL *samplesURL = [NSBundle.mainBundle.resourceURL URLByAppendingPathComponent:@"Samples"];
NSURL *logoURL = [samplesURL URLByAppendingPathComponent:@"PSPDFKit Logo.pdf"];

// Create the vector stamp annotation.
PSPDFStampAnnotation *vectorStampAnnotation = [[PSPDFStampAnnotation alloc] init];
vectorStampAnnotation.boundingBox = CGRectMake(50.f, 724.f, 200.f, 200.f);
vectorStampAnnotation.appearanceStreamGenerator = [[PSPDFFileAppearanceStreamGenerator alloc] initWithFileURL:logoURL];

// Add the vector stamp annotation to the document.
[document addAnnotations:@[vectorStampAnnotation] options:nil];

```

Refer to the [appearance streams](https://www.nutrient.io/../../annotations/appearance-streams/) guide for details.

### Adding watermarks

We then draw a “Generated for John Doe. Page X” watermark on every page, like this:

### SWIFT

```swift

// Create a default configuration.
let configuration = Processor.Configuration(document: document)!

configuration.drawOnAllCurrentPages { context, pageIndex, pageRect, renderOptions in
    // Careful. This code is executed on background threads. Only use thread-safe drawing methods.
    let name =  "John Doe"
    let text = "Generated for \(name). Page \(pageIndex + 1)"
    let stringDrawingContext = NSStringDrawingContext()
    stringDrawingContext.minimumScaleFactor = 0.1

    // Add text over the diagonal of the page.
    context.translateBy(x: 0, y: pageRect.size.height / 2)
    context.rotate(by: -.pi / 4)
    let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 30),.foregroundColor: UIColor.red.withAlphaComponent(0.5)
    ]
    text.draw(with: pageRect, options:.usesLineFragmentOrigin, attributes: attributes, context: stringDrawingContext)
}

// Start the conversion from `document` to `processedDocumentURL`.
let processor = Processor(configuration: configuration, securityOptions: documentSecurityOptions)
try processor.write(toFileURL: processedDocumentURL)

```

### OBJECTIVE-C

```objc

// Create default configuration.
PSPDFProcessorConfiguration *configuration = [[PSPDFProcessorConfiguration alloc] initWithDocument:document];

PSPDFRenderDrawBlock renderBlock = ^(CGContextRef context, PSPDFPageIndex pageIndex, CGRect pageRect, PSPDFRenderOptions *options) {
    // Careful. This code is executed on background threads. Only use thread-safe drawing methods.
    NSString *name =  @"John Doe";
    NSString *text = [NSString stringWithFormat:@"Generated for %@. Page %lu", name, pageIndex + 1];
    NSStringDrawingContext *stringDrawingContext = [NSStringDrawingContext new];
    stringDrawingContext.minimumScaleFactor = 0.1f;

    // Add text over the diagonal of the page.
    CGContextTranslateCTM(context, 0.f, pageRect.size.height/2.f);
    CGContextRotateCTM(context, -(CGFloat)M_PI / 4.f);
    [text drawWithRect:pageRect
               options:NSStringDrawingUsesLineFragmentOrigin
            attributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:100],
                         NSForegroundColorAttributeName: [UIColor.redColor colorWithAlphaComponent:0.5f]}
               context:stringDrawingContext];
};

[configuration drawOnAllCurrentPages:renderBlock];

// Start the conversion from `document` to `processedDocumentURL`.
PSPDFProcessor *processor = [[PSPDFProcessor alloc] initWithConfiguration:configuration securityOptions:nil];
[processor writeToFileURL:processedDocumentURL];

```

See the [adding watermarks](https://www.nutrient.io/../../features/document-processing/#adding-watermarks) section of our [document processing](https://www.nutrient.io/../../features/document-processing/) guide for more details.

Below is how the resulting page now appears.

### Adding form elements

Nutrient enables you to add, remove, and modify interactive form elements to allow things like user input or requesting a signature. These elements include text input, radio buttons, and checkboxes:

### SWIFT

```swift

// Create two radio buttons and position them in the document.
let radio1 = ButtonFormElement()
radio1.boundingBox = CGRect(x: 100, y: 100, width: 20, height: 20)
radio1.pageIndex = 0
let radio2 = ButtonFormElement()
radio2.boundingBox = CGRect(x: 130, y: 100, width: 20, height: 20)
radio2.pageIndex = 0

// `buttonValues` specifies the radio buttons' `onStateName`.
let buttonValues = ["RadioButton1", "RadioButton2"]

let radioButtonFormField = try ButtonFormField.insertedButtonField(with:.radioButton, fullyQualifiedName: "RadioButton", documentProvider: document.documentProviders.first!, formElements: [radio1, radio2], buttonValues: buttonValues)

```

### OBJECTIVE-C

```objc

// Create two radio buttons and position them in the document.
PSPDFButtonFormElement *radio1 = [[PSPDFButtonFormElement alloc] init];
radio1.boundingBox = CGRectMake(100.f, 100.f, 20.f, 20.f);
radio1.pageIndex = 0;
PSPDFButtonFormElement *radio2 = [[PSPDFButtonFormElement alloc] init];
radio2.boundingBox = CGRectMake(130.f, 100.f, 20.f, 20.f);
radio2.pageIndex = 0;

// `buttonValues` specifies the radio buttons' `onStateName`.
NSArray<NSString *> *buttonValues = @[@"RadioButton1", @"RadioButton2"];
NSError *error;
PSPDFButtonFormField *radioButtonFormField = [PSPDFButtonFormField insertedButtonFieldWithType:PSPDFFormFieldTypeRadioButton fullyQualifiedName:@"RadioButton" documentProvider:document.documentProviders[0] formElements:@[radio1, radio2] buttonValues:buttonValues
error:&error];
if (!radioButtonFormField) {
    // Handle error.
}

```

See our [form creation](https://www.nutrient.io/../../forms/form-creation/) guide for more details.

### Flatten annotations

Flattening annotations can be useful, as this prevents users from modifying them. In the sample code below, we flatten all annotation types except for link annotations:

### SWIFT

```swift

let processorConfiguration =...

// Flatten all annotations except for link annotations.
var types = Annotation.Kind.all
types.remove(.link)
processorConfiguration.modifyAnnotations(ofTypes: types, change:.flatten)

```

### OBJECTIVE-C

```objc

PSPDFProcessorConfiguration *processorConfiguration =...

// Flatten all annotations except for link annotations.
[processorConfiguration modifyAnnotationsOfTypes:PSPDFAnnotationTypeAll & ~PSPDFAnnotationTypeLink change:PSPDFAnnotationChangeFlatten];

```

See our [document processing](https://www.nutrient.io/../../features/document-processing/) guide for more details.

### Securing your PDF document

Nutrient enables you to secure a document against additional changes by adding a password:

### SWIFT

```swift

// Set owner password to only allow printing.
let processorConfiguration = Processor.Configuration(document: document)!
let ownerPassword = "test123"
let securedDocumentURL =...
let documentSecurityOptions = try Document.SecurityOptions(ownerPassword: ownerPassword, userPassword: nil, keyLength: Document.SecurityOptionsKeyLengthAutomatic, permissions: [.printing])
let processor = Processor(configuration: processorConfiguration, securityOptions: documentSecurityOptions)
try processor.write(toFileURL: securedDocumentURL)

// The newly processed `PSPDFDocument`.
let securedDocument = Document(url: securedDocumentURL)

```

### OBJECTIVE-C

```objc

// Set owner password to only allow printing.
PSPDFProcessorConfiguration *processorConfiguration = [[PSPDFProcessorConfiguration alloc] initWithDocument:document];
NSString *ownerPassword = @"test123";
NSURL *securedDocumentURL =...
PSPDFDocumentSecurityOptions *documentSecurityOptions = [[PSPDFDocumentSecurityOptions alloc] initWithOwnerPassword:ownerPassword userPassword:nil keyLength:PSPDFDocumentSecurityOptionsKeyLengthAutomatic permissions:PSPDFDocumentPermissionsPrinting error:NULL];
PSPDFProcessor *processor = [[PSPDFProcessor alloc] initWithConfiguration:processorConfiguration securityOptions:documentSecurityOptions];
[processor writeToFileURL:securedDocumentURL error:NULL];

// The newly processed `PSPDFDocument`.
PSPDFDocument *securedDocument = [[PSPDFDocument alloc] initWithURL:securedDocumentURL];

```

See our [creating a password-protected document](https://www.nutrient.io/../../features/document-processing/#creating-a-password-protected-document) guide for more details.
---

## Related pages

- [Generate blank PDFs on iOS](/guides/ios/features/document-creation.md)
- [Generate PDFs from images on iOS](/guides/ios/pdf-generation/from-images.md)
- [Generate PDFs from HTML on iOS](/guides/ios/pdf-generation/from-html.md)
- [Generate PDFs from a PDF form in iOS](/guides/ios/pdf-generation/from-pdf-form.md)
- [PDF generation library for iOS](/guides/ios/pdf-generation.md)
- [Generate PDFs from templates on iOS](/guides/ios/miscellaneous/custom-page-templates.md)
- [Generate a password-protected PDF on iOS](/guides/ios/pdf-generation/password-protected-pdf.md)
- [Generate PDFs programmatically on iOS](/guides/ios/pdf-generation/programmatically.md)
- [Generate PDF thumbnails on iOS](/guides/ios/pdf-generation/thumbnail-preview.md)

