4

I would like to make the Text Widget PDFAnnotation readonly. I tried to set the isReadOnly flag to true, but it doesn't seem to make any difference. The user is still able to edit the annotation after tapping it.

Chris Garrett
  • 4,824
  • 1
  • 34
  • 49
crcalin
  • 979
  • 6
  • 13
  • Got the answer ? – Mohamed Raffi Jul 24 '18 at 17:17
  • No, but I've used a FreeText Annotation instead. And when I want to edit it, I'm placing a UITextView over the annotation. I believe this is exactly how Text Annotation work under the hood, because I'm getting the same UX. Also, I have more control over the editing and I'm able to immediately show the keyboard by selecting the text. Using Text annotation I was not able to make it 'first responder'. – crcalin Jul 25 '18 at 08:41
  • Apple's support for annotation flags is quite poor, we did write an overview here: https://pspdfkit.com/guides/web/current/annotations/annotation-flags/ – steipete Aug 14 '18 at 07:09

2 Answers2

2

I have been working on this problem for a while now and I finally found something that works. The solution for me was to use a .widget over .freeText. This method keeps the text from being selected and modified after export. I should point out that PDFs are not infallible, decompilation is possible with any PDF but for the every day office worker this is a perfect solution.

 //Don't use this - This is what most tutorials show
 //Which can be easily changed after export
 let annotation = PDFAnnotation(bounds: CGRect(x: 10 , y: 720 , width: 100, height: 50), forType: .freeText, withProperties: nil)

Use this - Swift 5

func addText(x: Int, y: Int, width: Int, height: Int, text: String, fontSize: CGFloat, centerText: Bool){
    
    //I'm using a PDFView but, if you are not displaying the PDF in the app then just use
    //let fileURL = Bundle.main.url(forResource: "MyPDF", withExtension: "pdf")!
    //let pdfDocument = PDFDocument(url: fileURL)
    //guard let document = pdfDocument else { return }

    guard let document = pdfView.document else { return }
    //I'm only using one page for my PDF but this is easy to change
    let lastPage = document.page(at: document.pageCount - 1)
    let annotation = PDFAnnotation(bounds: CGRect(x: x , y: y, width: width, height: height), forType: .widget, withProperties: nil)

    //Don't use contents and caption
    //annotation.contents = text
    //annotation.caption = text

    //Use this instead
    annotation.widgetFieldType = .text
    annotation.widgetStringValue = text

    //Check if the text should be centered
    if (centerText){ annotation.alignment = .center }

    //I'm using a custom font 
    annotation.font = UIFont(name: "calibri", size: fontSize)
    //You can use this instead
    //annotation.font = UIFont.systemFont(ofSize: fontSize)

    //Set the color
    annotation.fontColor = .black
    annotation.color = .clear
    annotation.backgroundColor = .clear

    //This is useless 
    annotation.isReadOnly = true

    //Add the annotation to the last page
    lastPage?.addAnnotation(annotation)
    
}
Greg432
  • 530
  • 4
  • 25
0

It seems to be a bug/oversight that PDFKit doesn't honor the isReadOnly attribute on annotations. However I was able to work around this by adding a blank annotation over other annotations in the document. I added a makeReadOnly() extension to PDF document that does this for all annotations to make the whole document read only. Here's the code:

// A blank annotation that does nothing except serve to block user input
class BlockInputAnnotation: PDFAnnotation {

    init(forBounds bounds: CGRect, withProperties properties: [AnyHashable : Any]?) {
        super.init(bounds: bounds, forType: PDFAnnotationSubtype.stamp,  withProperties: properties)
        self.fieldName = "blockInput"
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(with box: PDFDisplayBox, in context: CGContext)   {
    }
}


extension PDFDocument {
    func makeReadOnly() {
        for pageNumber in 0..<self.pageCount {
            guard let page = self.page(at: pageNumber) else {
                continue
            }
            for annotation in page.annotations {
                annotation.isReadOnly = true // This _should_ be enough, but PDFKit doesn't recognize the isReadOnly attribute
                // So we add a blank annotation on top of the annotation, and it will capture touch/mouse events
                let blockAnnotation = BlockInputAnnotation(forBounds: annotation.bounds, withProperties: nil)
                blockAnnotation.isReadOnly = true
                page.addAnnotation(blockAnnotation)
            }
        }

    }
}
Chris Garrett
  • 4,824
  • 1
  • 34
  • 49
  • 1
    this will fix the problem only inside the app, If you try to save the PDF and then open it on any PDF editor or Preview app in MacOS, you will notice that you can move annotations and also you can delete it ! – Basel Feb 13 '19 at 19:51
  • In iOS13 the bug in `isReadOnly` attribute on annotations seems to be fixed – Giorgio Dec 16 '19 at 14:32