PDFKit add UITextField over PDFPage

Hi,

I use PDFKit in iOS as a pdf document renderer.

In me case I need "flatten all form fields" and render it in me custom layer over pdf page.

This is because I need to have full control over the fields.


I can render watermark over every page when I registred classForPage in ViewController as:

func classForPage() -> AnyClass { 
     return CustomPage.self 
}

CustomPage class:

class CustomPage: PDFPage { 
     override func draw(with box: PDFDisplayBox, to context: CGContext { 

          super.draw(with: box, to: context)

          UIGraphicsPushContext(context)
          context.saveGState()

          let string: NSString = "Custom string" 
          string.draw(at: CGPoint(x:250, y:40), withAttributes: attributes) 
 
          context.restoreGState() 
          UIGraphicsPopContext() 
     }
}

I need to add custom UITextField in a similar way but when I try:

 
let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40)) 
sampleTextField.placeholder = "Enter text here" 
sampleTextField.draw(CGRect(x: 20, y: 100, width: 300, height: 40))

in run time XCode show warning "-[UITextField initWithFrame:] must be used from main thread only" and UITextField is not rendered.


If I try to encapsulate the main thread:

DispatchQueue.main.async { }

warning from XCode not showed but UITextField still not rendered.


I expect new UITextField on page 1, but the actual is page clear, without it.


I want to render custom active element like as textfield, radio button, checkbox and signature field.

Accepted Reply

Remove the line as I recommended:


// REMOVE THIS sampleTextField.draw(CGRect(x: 20, y: 100, width: 300, height: 40))


You may also have to get

sampleTextField.text = "Some text you entered"

out of

UIGraphicsPushContext(context)

Replies

Could you show the complete code of the class ?


And how you create the instance of PDFPage.


Depending how you create sampleTextField = UITextField(), it may not exist beyond where you define it.

You should probably declare a var at class level as UITextField?, and create in init.


Then, us in draw.

But once again, need to see more code.

Hi,


I have ViewController:

import UIKit
import PDFKit

class ViewController: UIViewController, PDFDocumentDelegate {
    
    @IBOutlet weak var pdfView: PDFView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        if let documentURL = Bundle.main.url(forResource: "Untitled12", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {
                
                pdfView?.autoScales = true
                pdfView?.backgroundColor = UIColor.lightGray
                
                document.delegate = self
                pdfView?.document = document   
            }
        }
        
    }
  
    func classForPage() -> AnyClass {
        return CustomPage.self
    }
}


And CustomPage:

class CustomPage: PDFPage {


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

        super.draw(with: box, to: context)

        UIGraphicsPushContext(context)
        context.saveGState()

        let pageBounds = self.bounds(for: box)
        context.translateBy(x: 0.0, y: pageBounds.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        context.rotate(by: CGFloat.pi / 4.0)

        let string: NSString = "Custom string"
        let attributes = [
            NSAttributedStringKey.foregroundColor: UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.5),
            NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 64)
        ]

        // THIS WORK PERFECTLY:
        string.draw(at: CGPoint(x:250, y:40), withAttributes: attributes)

        // THIS DOESNT WORK:
        let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
        sampleTextField.placeholder = "Enter text here"
        sampleTextField.font = UIFont.systemFont(ofSize: 15)
        sampleTextField.autocorrectionType = UITextAutocorrectionType.no

        sampleTextField.draw(CGRect(x: 20, y: 100, width: 300, height: 40))


        context.restoreGState()
        UIGraphicsPopContext()

    }
}


I want to render textfields, radio button, checkboxies and signature fileds.


In signature fields I want to add custom tapped action when I tapped to annotation.

I did not test, but I would try:


class CustomPage: PDFPage {

     var sampleTextField: UITextField!

    override init() {
        super.init()
        sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
        sampleTextField.placeholder = "Enter text here"
        sampleTextField.font = UIFont.systemFont(ofSize: 15)
        sampleTextField.autocorrectionType = UITextAutocorrectionType.no
    }

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

        super.draw(with: box, to: context)

        UIGraphicsPushContext(context)
        context.saveGState()

        let pageBounds = self.bounds(for: box)
        context.translateBy(x: 0.0, y: pageBounds.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        context.rotate(by: CGFloat.pi / 4.0)

        let string: NSString = "Custom string"
        let attributes = [
            NSAttributedStringKey.foregroundColor: UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 0.5),
            NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 64)
        ]

        // THIS WORK PERFECTLY:
        string.draw(at: CGPoint(x:250, y:40), withAttributes: attributes)

        // THIS DOESNT WORK:
       /*  REMOVE HERE  let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
        sampleTextField.placeholder = "Enter text here"
        sampleTextField.font = UIFont.systemFont(ofSize: 15)
        sampleTextField.autocorrectionType = UITextAutocorrectionType.no. */

        sampleTextField.draw(CGRect(x: 20, y: 100, width: 300, height: 40))     // WHY DO YOU DRAW HERE ? IT WOULD BE ENOUGHT TO SET CONTENT
       sampleTextField.text = "Some text you entered" // If you need to change, but that should be just what was typed in

        context.restoreGState()
        UIGraphicsPopContext()

    }
}

Still not working,


but you have right, it is not a good idea to draw textfield directly, but I should set it in context.


How do I set it there?

Remove the line as I recommended:


// REMOVE THIS sampleTextField.draw(CGRect(x: 20, y: 100, width: 300, height: 40))


You may also have to get

sampleTextField.text = "Some text you entered"

out of

UIGraphicsPushContext(context)