iOS PDFKit tutorial: Text Annotations & more
Tags: ios, swift, pdfkit
Published: 2023-03-19 13:35:59

This is the second article about Apple’s PDFkit featuring working with Text Annotations, document auto-saving and PencilKit.

- [First](https://dev.to/artem_poluektov/ios-pdfkit-ink-annotations-tutorial-4kgh) article is about PDFKit basics & Ink annotations
- Third article is about creating PDF document on device and inserting/removing pages

## PencilKit
We tried to implement `PencilKit` support in our app right after iOS 13 release. We even created a ticket to Apple Technical Support asking is there any easy way to do so. Short answer is **NO**.
You just can't use `PencilKit` together with `PDFKit` like how it works in native iOS markup screens.

Apple Technical Support answer:

> _The PencilKit team recommends developers put a PKCanvasView over each PDFPage. Signatures are a little different - you'd probably need to grab the PKDrawing from the PKCanvasView and then render the PKDrawing as an image inside of a PDFPage as you would a watermark._

Proposed solution seems to be impossible to implement (adding `PKCanvasView` to `PDFPage`). Second part (about grabbing `PKDrawing`) seems to be not the right way for our task too because of requirement to zoom page and erase previously added annotations. So, I feel that our solution from the first part of this tutorial is still the right one.

`PKToolPicker` seems not to be the right fit as well. It looks much better than out instruments view, but is completely not customizable. For example, in iOS 13 it features ruler, and if we don't need it in our app, we're still unable to remove it from ToolPicker. Hope, Apple would add some ways for customization in future releases.

---

## Text annotations

Text annotations seems to be easier than drawing (Ink). However due to lack of documentation and sample code (again!) it wasn't an easy task.

So, let's assume we need to add some "Hello, world!" text to some place on a document's last page. To do so, write the code below (in your Drawing View Controller, for this example):

```
func addDateAnnotation() {
  guard let document = pdfView.document else { return }
  let lastPage = document.page(at: document.pageCount - 1)
  let annotation = PDFAnnotation(bounds: CGRect(x: 100, y: 100, width: 100, height: 20), forType: .freeText, withProperties: nil)
  annotation.contents = "Hello, world!"
  annotation.font = UIFont.systemFont(ofSize: 15.0)
  annotation.fontColor = .blue
  annotation.color = .clear
  lastPage?.addAnnotation(annotation)
}
```

First, you need a page where you want to add annotation. Then you need to set following properties:

- `contents`: text to display,
- `font`: font,
- `fontColor`: foreground color,
- `color`: background color.

That's it. Just don't forget to save your document.

---

## Annotation types

In one of our tasks we needed to iterate through all annotations array to remove annotation of the specific type. We found that calling `PDFAnnotationSubtype.freeText` and `annotation.type` would actually return different results!

```
PDFAnnotationSubtype.freeText // "/freeText"
annotation.type // "FreeText"
```

So, to filter annotations of the specific type we had to call:

```
allPageAnnotations.filter { $0.type == "FreeText" }
```
instead of

```
allPageAnnotations.filter { $0.type == PDFAnnotationSubtype.freeText }
```

---

## Next & Previous buttons

Implementing next & previous buttons [which are very useful for your users] is very easy. Just add those buttons in your Storyboard and call one of those methods:

```
pdfView.goToPreviousPage(nil)
// or
pdfView.goToNextPage(nil)
```

---

## Auto-saving the document

Due to our long history with crashes we decided to implement auto-saving feature. We wanted to save PDFDocument after each successfully drawn annotation and used this code:

```
pdfDocument.write(to: url)

```
However, with larger documents this code caused UI freezes, so we tried to do it in background:

```
DispatchQueue.global(qos: .background).async {
  pdfDocument.write(to: url)
}
```
Which caused crashes when document's content changed during saving.
Our final solution was to use `PDFDocument` method `dataRepresentation` combined with simple `Timer`:

```
if let data = pdfDocument.dataRepresentation() {
  try? data.write(to: url)
}
```
This solution didn't cause crashes, but made `PDFView` blink on this call. The only working solution we found was:

- create a copy of `PDFDocument` ,
- apply all changes (adding/removing annotations) to visible document in `PDFView` and to copy, only add completed annotations due to performance,
- save copy each 30 seconds (for example),
- track all changes during saving: you cannot apply changes during save, so need to store changes history in memory during saving, which may take some time with a bigger files.

---
Useful links
You would find some initial information about PDFKit in this WWDC video:

- [https://developer.apple.com/videos/play/wwdc2017/241/](https://developer.apple.com/videos/play/wwdc2017/241/)

Here you'll find Apple's sample code of advanced drawing with Apple Pencil. Not sure it would work really great with PDFs due to performance issues.

- [https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/leveraging_touch_input_for_drawing_apps](https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/leveraging_touch_input_for_drawing_apps)

If you're looking for any alternative solution, check one of these frameworks. I haven't found any free or open-source solutions, and licenses for those in the list are pretty expensive ($500-$1K+).
PSPDFKit. Features drop-in replacement APIs for Apple's 

- PDFKit,
- Foxit,
- PDFTron.