Generate PDF reports on iOS

Generate On-Device PDF Reports on iOS header

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 on our blog.

Generating PDF documents

With a combination of features like the Document Editor and annotations, Nutrient can create professional reports based on a template and include custom cover and final pages. See the ReportPDFGenerationExample.swift(opens in a new tab) example from the Catalog app 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.

template-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:

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)")
}

Adding text and images

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

// 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])

See our 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:

// 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])

Refer to the appearance streams guide for details.

Adding watermarks

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

// 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)

See the adding watermarks section of our document processing guide for more details.

Below is how the resulting page now appears.

annotations-page

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:

// 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)

See our 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:

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

See our document processing guide for more details.

Securing your PDF document

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

// 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)

See our creating a password-protected document guide for more details.