skip to Main Content

I’m trying to get PDF from UIView with UILabel text mask.

  override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 200 ))
        label.text = "Label Text"
        label.font = UIFont.systemFont(ofSize: 25)
        label.textAlignment = .center
        label.textColor = UIColor.white

        let overlayView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200 ))
        overlayView.image = UIImage(named: "erasemask.png")
        
        label.mask = overlayView
        view_process.addSubview(label)
        
    }

   func exportAsPdfFromView(){

        let pdfPageFrame = CGRect(x: 0, y: 0, width: view_process.bounds.size.width, height: view_process.bounds.size.height)

        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
        UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)

        guard let pdfContext = UIGraphicsGetCurrentContext() else { return "" }
       
        view_process.layer.render(in: pdfContext)
        UIGraphicsEndPDFContext()
          
       let path = self.saveViewPdf(data: pdfData)
        print(path)

      }

    func saveViewPdf(data: NSMutableData) -> String {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let docDirectoryPath = paths[0]
        let pdfPath = docDirectoryPath.appendingPathComponent("viewPdf.pdf")
        if data.write(to: pdfPath, atomically: true) {
            return pdfPath.path
        } else {
            return ""
        }
      }

but I do not get PDF with mask. I don’t want to convert UIView to UImage and then convert UImage to PDF. I want to editable PDF so don’t want to convert into UIImage.

Can anyone help me How to convert Masked UILabel to PDF ?

here erasemask.png erasemask.png

2

Answers


  1. You have to draw text yourself in the pdf to make it editable.

    I have slightly modified your code, and it works. You can simply copy/paste in a new app view controller to check the result.

    Here is a screenshot of the document opened in AffinityDesigner. The mask is correctly applied as a layer mask, and the text is editable.

    enter image description here

    It needs some tweaking to respect exact layout.

    import UIKit
    
    class ViewController: UIViewController {
        
        var labelFrame: CGRect { return CGRect(x: 0, y: 0, width: 200, height: 200 )}
        
        lazy var label: UILabel = {
            let label = UILabel(frame: labelFrame)
            label.text = "Label Text"
            label.font = UIFont.systemFont(ofSize: 25)
            label.textAlignment = .center
            label.textColor = UIColor.black
            return label
        }()
        
        var maskImage = UIImage(named: "erasemask.png")
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(label)
            let overlayView = UIImageView(frame: labelFrame)
            overlayView.image = maskImage
            
            label.mask = overlayView
    
            exportAsPdfFromView()
        }
        
        func exportAsPdfFromView() {
            
            let pdfPageFrame = CGRect(x: 0, y: 0, width: view.bounds.size.width, height: view.bounds.size.height)
            
            let pdfData = NSMutableData()
            UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
            UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)
            guard let context = UIGraphicsGetCurrentContext() else { return }
                    
            // Clip context
            if let overlayImage = maskImage?.cgImage {
                context.clip(to: labelFrame, mask: overlayImage)
            }
            
            // Draw String
            let string: String = label.text ?? ""
            let attributes: [NSAttributedString.Key: Any] = [
                NSAttributedString.Key.foregroundColor: label.textColor ?? .black,
                NSAttributedString.Key.font: label.font ?? UIFont.systemFont(ofSize: 25)
            ]
            let stringRectSize = string.size(withAttributes: attributes)
            let x = (labelFrame.width - stringRectSize.width) / 2
            let y = (labelFrame.height - stringRectSize.height) / 2
            let stringRect = CGRect(origin: CGPoint(x: x, y: y), size: stringRectSize)
            string.draw(in: stringRect, withAttributes: attributes)
    
            UIGraphicsEndPDFContext()
            
            saveViewPdf(data: pdfData)
        }
        
        func saveViewPdf(data: NSMutableData) {
            guard let docDirectoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
            let pdfPath = docDirectoryPath.appendingPathComponent("viewPdf.pdf")
            print(pdfPath)
            data.write(to: pdfPath, atomically: true)
        }
    }
    

    EDITED

    As a comparison, I have added the result by using the Apple view.render function, using code from the question ( with a blue background so we can see white text ). It clearly shows that this function does not support editable text and masking.
    It exports the document as a stack of flat images and extra – useless – groups.
    So I guess the only solution to keep pdf entities types is to compose the document by rendering each object.

    If you need to export complex forms, it must not be too hard to make a helper that crawls in the view hierarchy and render each object content.

    If you need to also render pdf with interactive buttons, you will probably need to use Annotations( available in PDFKit ). By the way you can also create editable fields with annotations, but I’m not sure it supports masking.

    enter image description here

    LAST EDIT:

    Since you use an external framework to render pdf ( PDFGenerator ), it is different. You can only override the view.layer.render function that is used by the framework and use the context.clip function.

    To export ui components as editable, I think they must have no superview. As soon as there is a view hierarchy, a backing bitmap is created, and all render calls are made with this bitmap as parameter, and not the PDFContext that was used to call the first render.

    That’s how PDFGenerator works, it crawls in the view hierarchy an render views by removing them from superview, render to the context, and move them back in hierarchy.

    The big drawback of this is that it leads to bugs when the view is moved back in the hierarchy. Probably because constraints are lost, or some views like UIStackView behave differently. There is numerous opened issues around this on their github.

    What I don’t really understand yet is why the label below is rendered as editable even in a view hierarchy. I guess it’s due to how the render function is done by Apple. Can’t go further on this right now..

    Custom Field Example:

    It may need more work to respect exact field layout, but this is a basis for a view that is rendered as editable text. It gives the exact same result as in the first part of the answer.

    • It works with any mask view, not only UIImage.
    • It is rendered as editable even in a hierarchy.

    So if you use this label instead of UILabel in the initial code of this question, it works.

    // Extension to get mask view as an image
    extension UIView {
        var cgImage: CGImage? {
            UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, contentScaleFactor);
            guard let context = UIGraphicsGetCurrentContext() else { return nil }
            layer.render(in: context)
            let image = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            return image?.cgImage
        }
    }
    
    class PMLayer: CALayer {
        // Needed to access text style
        weak var label: UILabel?
        
        override init() { super.init() }
        required init?(coder: NSCoder) { super.init(coder: coder) }
        
        override init(layer: Any) {
            super.init(layer: layer)
            if let pm = layer as? PMLayer {
                label = pm.label
            }
        }
        
        override func render(in ctx: CGContext) {
            guard let label = label else { return }
            
            // Clip context
            if let mask = label.mask?.cgImage {
                ctx.clip(to: label.frame, mask: mask)
            }
            
            // Draw String
            let string: String = label.text ?? ""
            let attributes: [NSAttributedString.Key: Any] = [
                NSAttributedString.Key.foregroundColor: label.textColor ?? .black,
                NSAttributedString.Key.font: label.font ?? UIFont.systemFont(ofSize: 25)
            ]
            let stringRectSize = string.size(withAttributes: attributes)
            let x = (label.frame.width - stringRectSize.width) / 2
            let y = (label.frame.height - stringRectSize.height) / 2
            let stringRect = CGRect(origin: CGPoint(x: x, y: y), size: stringRectSize)
            string.draw(in: stringRect, withAttributes: attributes)
        }
    }
    
    class PMLabel: UILabel {
        override class var layerClass: AnyClass { return PMLayer.self }
    
        // Be sure the label is correctly linked to the layer when we access it
        override var layer: CALayer {
            (super.layer as? PMLayer)?.label = self
            return super.layer
        }
    }
    
    Login or Signup to reply.
  2. this is my decision

    import UIKit
    import PDFKit
    
    class ViewController: UIViewController {
        
        @IBOutlet weak var view_process: UIView!
        @IBOutlet weak var pdf_view: UIView!
        let documentInteractionController = UIDocumentInteractionController()
        @IBAction func bClick(_ sender: Any) {
            exportAsPdfFromView()
        }
        override func viewDidLoad() {
            super.viewDidLoad()
            documentInteractionController.delegate = self
            let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 200 ))
            label.text = "Label Text"
            label.font = UIFont.systemFont(ofSize: 25)
            label.textAlignment = .center
            label.textColor = UIColor.blue
            
            let overlayView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200 ))
            overlayView.image = UIImage(named: "erasemask.png")
            
            label.mask = overlayView
            view_process.addSubview(label)
            
        }
        
        func exportAsPdfFromView(){
            
            let pdfPageFrame = view_process.bounds//.CGRect(x: 0, y: 0, width: view_process.bounds.size.width, height: view_process.bounds.size.height)
            
            let pdfData = NSMutableData()
            UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
            UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)
            
            let pdfContext = UIGraphicsGetCurrentContext()!
            
            view_process.layer.render(in: pdfContext)
            UIGraphicsEndPDFContext()
            
            let path = self.saveViewPdf(data: pdfData)
            print(path)
            self.share(url: URL(string: "file://(path)")!)
        }
        
        func saveViewPdf(data: NSMutableData) -> String {
            let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
            let docDirectoryPath = paths[0]
            let pdfPath = docDirectoryPath.appendingPathComponent("viewPdf.pdf")
            if data.write(to: pdfPath, atomically: true) {
                return pdfPath.path
            } else {
                return ""
            }
        }
        
        func openPdf(path: String) {
            let pdfView = PDFView(frame: self.view.bounds)
            pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            pdf_view.addSubview(pdfView)
            
            // Fit content in PDFView.
            pdfView.autoScales = true
            
            // Load Sample.pdf file from app bundle.
            let fileURL = URL(string: path)
            pdfView.document = PDFDocument(url: fileURL!)
        }
        
        
        func share(url: URL) {
            documentInteractionController.url = url
            //        documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
            //        documentInteractionController.name = url.localizedName ?? url.lastPathComponent
            documentInteractionController.presentPreview(animated: true)
        }
    }
    
    extension ViewController: UIDocumentInteractionControllerDelegate {
        func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
            guard let navVC = self.navigationController else {
                return self
            }
            return navVC
        }
    }
    

    result pdf with blue label color

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search