Search code examples
iosipaduibezierpathpdfkit

iOS 11 PDFKit Ink annotation - cannot fill UIBezierPath


I'm adding ink annotations to PDFDocument using the PDFAnnotation() class with subtype ink. The idea is to capture signatures that are drawn using touch.

Inspired by UberSignature my UIBezierPath is a series of rectangles that are supposed to be filled with a color. However, when I add the annotation to the PDFDocument it is not filled.

It seems the UIBezierPath's fill() method does nothing at all when added to a PDFAnnotation?

If I use the same UIBezierPath and draw it on an UIImage it is correctly filled with a solid color.

Any ideas on what could be wrong?

Problematic code:

UIColor.red.setStroke()
UIColor.red.setFill()

var path = UIBezierPath()
path.append(myRectangles)
path.fill()

var annotation = PDFAnnotation(bounds: path.bounds, forType: .ink, withProperties: nil)
annotation.add(path)
myPDFPage.addAnnotation(annotation)

Current view of PDF

In the screenshot I have tried to write normal text, and two example lines. The line on the left is drawn slowly, the line on the right is drawn fast. The idea is to have width of the line vary according to the speed it is drawn to make a signature look more natural/realistic.


Solution

  • I managed to find a solution to the problem that seems relatively optimal.

    The trick is do create a subclass of PDFAnnotation and override the draw(with box:, in context:) function. In this function I can use the drawPath(using: .fill) method to fill out bezier path.

    The code can look like this:

    class SignatureAnnotation : PDFAnnotation {
      public var myPath : UIBezierPath = UIBezierPath()
    
      override func draw(with box: PDFDisplayBox, in context: CGContext) {
        context.saveGState()
        self.page?.transform(context, for: box)
        context.beginPath()
        context.setLineWidth(0.1)
        context.setShouldAntialias(true)
        context.addPath(self.myPath.cgPath.mutableCopy()!)
        context.drawPath(using: .fill)
        context.restoreGState()
      }
    }
    

    Add this annotation (type .stamp) to the PDF instead of the ink annotation, and everything will be rendered as vector (fully zoomable without being pixelated) - and will be saved together with the rest of the PDF when saved to file or data buffer.

    Only drawback is that the UIBezierPath cannot be too complicated as flickering will be introduced if the draw() function takes too long. This can be solved by simply splitting up the UIBezierPath into multiple seperate paths that each has its own annotation.