【问题标题】:Get PDF from a UILabel text mask从 UILabel 文本掩码中获取 PDF
【发布时间】:2021-06-26 09:55:47
【问题描述】:

我正在尝试使用 UILabel 文本掩码从 UIView 获取 PDF。

  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 ""
        }
      }

但我没有得到带有掩码的 PDF。我不想将 UIView 转换为 UImage,然后将 UImage 转换为 PDF。 我想要可编辑的 PDF,所以不想转换成 UIImage。

谁能帮助我如何将 Masked UILabel 转换为 PDF?

这里是erasemask.png

【问题讨论】:

  • 您希望将蒙版标签作为 PDF 中的文本吗? PDF可以处理这个吗?
  • @paiv 你明白我的意思了吗?

标签: ios swift xcode uilabel mask


【解决方案1】:

您必须自己在 pdf 中绘制文本以使其可编辑。

我稍微修改了您的代码,并且可以正常工作。您可以简单地复制/粘贴到新的应用视图控制器中以检查结果。

这是在 AffinityDesigner 中打开的文档的屏幕截图。蒙版正确应用为图层蒙版,并且文本可编辑。

它需要一些调整以尊重精确的布局。

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)
    }
}

已编辑

作为比较,我使用 Apple view.render 函数添加了结果,使用了问题中的代码(蓝色背景,因此我们可以看到白色文本)。它清楚地表明此功能不支持可编辑文本和遮罩。 它将文档导出为一堆平面图像和额外的 - 无用的 - 组。 所以我想保留 pdf 实体类型的唯一解决方案是通过渲染每个对象来组成文档。

如果需要导出复杂的表单,制作一个在视图层次结构中爬行并渲染每个对象内容的助手一定不会太难。

如果您还需要使用交互式按钮呈现 pdf,您可能需要使用 Annotations(在 PDFKit 中提供)。顺便说一句,您还可以创建带有注释的可编辑字段,但我不确定它是否支持屏蔽。

最后编辑:

由于您使用外部框架来呈现 pdf ( PDFGenerator ),所以它是不同的。您只能覆盖框架使用的view.layer.render 函数并使用context.clip 函数。

要将 ui 组件导出为可编辑,我认为它们必须没有超级视图。一旦存在视图层次结构,就会创建一个支持位图,并且所有渲染调用都使用此位图作为参数,而不是用于调用第一个渲染的 PDFContext。

这就是 PDFGenerator 的工作原理,它在视图层次结构中爬行,通过从超级视图中移除视图、渲染到上下文并将它们移回层次结构中来呈现视图。

这样做的最大缺点是当视图在层次结构中移回时会导致错误。可能是因为约束丢失了,或者像 UIStackView 这样的视图表现不同。在他们的 github 上有许多未解决的问题。

我还不太明白为什么下面的标签即使在视图层次结构中也呈现为可编辑的。我想这是因为render 函数是由 Apple 完成的。现在不能再进一步了..

自定义字段示例:

遵守精确的字段布局可能需要更多的工作,但这是呈现为可编辑文本的视图的基础。它给出的结果与答案的第一部分完全相同。

  • 它适用于任何遮罩视图,不仅是 UIImage。
  • 即使在层次结构中也呈现为可编辑。

所以如果你在这个问题的初始代码中使用这个标签而不是 UILabel,它就可以工作。

// 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
    }
}

【讨论】:

  • 您检查过我们保存的 pdf 吗?我没有得到任何面具。
  • 使用哪个代码 - 原始代码还是我的答案?在我的回答中,我在屏幕和 pdf 中得到完全相同的结果。带遮罩的可编辑文本。这不是你想要的吗?
  • 我已经重试了你的代码以确保。它将遮罩导出为叠加层。我猜这是 Apple 的 .render 函数中的错误或不受支持的功能。看起来它将所有图层拆分并导出为平面图像。你运行我的解决方案了吗?在我的 Mac 上,在模拟器中,我在应用程序和导出的 pdf 中得到完全相同的结果。
  • 刚刚添加了一个截图来演示render函数的结果。 @Hector,这是一个有趣的问题;)
  • 我想我通过剪辑子视图层得到了解决方案
【解决方案2】:

这是我的决定

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
    }
}

【讨论】:

  • 请给 view_process 添加一些背景颜色,这样你就可以看到我们没有得到我们想要的结果
  • 你能不能展示一下,可能是一张图片,你想要什么?
猜你喜欢
  • 2014-05-05
  • 1970-01-01
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
  • 2015-06-28
  • 1970-01-01
  • 2011-06-14
相关资源
最近更新 更多