DEV Community

Artem Poluektov
Artem Poluektov

Posted on

iOS PDFKit tutorial: Text Annotations & more

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

  • First 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)
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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

allPageAnnotations.filter { $0.type == "FreeText" }
Enter fullscreen mode Exit fullscreen mode

instead of

allPageAnnotations.filter { $0.type == PDFAnnotationSubtype.freeText }
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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:

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.

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.

Top comments (0)