You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS含NSTextAttachment图片的NSAttributedString转PDF无图显示求助

解决UITextView富文本含NSTextAttachment转PDF图片不显示问题

我之前也踩过一模一样的坑:用UIPrintPageRenderer配合UITextView的viewPrintFormatter()生成PDF时,NSTextAttachment的图片只会留下空白占位,完全不渲染实际内容。这本质上是系统的UIPrintFormatter在处理富文本附件时,没有正确触发图片的绘制逻辑。下面给你两种经过验证的可行方案:

方法一:自定义UIPrintFormatter手动渲染富文本

我们可以自己实现一个UIPrintFormatter子类,借助CoreText的排版系统来绘制内容,这样能确保NSTextAttachment里的图片被正确渲染出来。

自定义PrintFormatter实现

class AttributedTextPrintFormatter: UIPrintFormatter {
    let attributedText: NSAttributedString
    private var textContainer: NSTextContainer!
    private var layoutManager: NSLayoutManager!
    private var textStorage: NSTextStorage!
    
    init(attributedText: NSAttributedString) {
        self.attributedText = attributedText
        super.init()
        setupTextLayoutSystem()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupTextLayoutSystem() {
        textStorage = NSTextStorage(attributedString: attributedText)
        layoutManager = NSLayoutManager()
        textContainer = NSTextContainer(size: .zero)
        
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
    }
    
    override func rectForPage(at pageIndex: Int) -> CGRect {
        return printableRect
    }
    
    override func draw(in rect: CGRect, forPageAt pageIndex: Int) {
        let glyphRange = layoutManager.glyphRange(forPageAt: pageIndex, in: textContainer)
        layoutManager.drawGlyphs(forGlyphRange: glyphRange, at: rect.origin)
    }
    
    override func calculatePrintFormats(forPageRange range: NSRange, printRenderer renderer: UIPrintRenderer) {
        textContainer.size = renderer.printableRect.size
        let pageCount = layoutManager.numberOfPages(for: textContainer)
        self.pageRange = NSMakeRange(0, pageCount)
    }
}

修改你的createPDF方法

把原来依赖的UISimpleTextPrintFormatter换成我们自定义的类:

public func createPDF(text: NSAttributedString, documentPath: URL, customPrintFormatter: UIPrintFormatter? = nil) {
    let printFormatter: UIPrintFormatter
    if let customFormatter = customPrintFormatter {
        printFormatter = customFormatter
    } else {
        // 使用自定义Formatter处理富文本附件
        printFormatter = AttributedTextPrintFormatter(attributedText: text)
    }
    
    let renderer = UIPrintPageRenderer()
    renderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)
    
    // A4尺寸设置
    let pageSize = CGSize(width: 595.2, height: 841.8)
    let pageMargins = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)
    let printableRect = CGRect(x: pageMargins.left, y: pageMargins.top, 
                               width: pageSize.width - pageMargins.left - pageMargins.right, 
                               height: pageSize.height - pageMargins.top - pageMargins.bottom)
    let paperRect = CGRect(x: 0, y: 0, width: pageSize.width, height: pageSize.height)
    
    renderer.setValue(NSValue(cgRect: paperRect), forKey: "paperRect")
    renderer.setValue(NSValue(cgRect: printableRect), forKey: "printableRect")
    
    let pdfData = NSMutableData()
    UIGraphicsBeginPDFContextToData(pdfData, paperRect, nil)
    renderer.prepare(forDrawingPages: NSMakeRange(0, renderer.numberOfPages))
    let bounds = UIGraphicsGetPDFContextBounds()
    
    for i in 0 ..< renderer.numberOfPages {
        UIGraphicsBeginPDFPage()
        renderer.drawPage(at: i, in: bounds)
    }
    
    UIGraphicsEndPDFContext()
    
    do {
        try pdfData.write(to: documentPath)
    } catch {
        print(error.localizedDescription)
    }
}

方法二:直接用CoreGraphics绘制到PDF上下文

如果觉得自定义Formatter太繁琐,也可以跳过UIPrintPageRenderer,直接将富文本逐页绘制到PDF上下文,这种方式更直接,也能保证图片正常显示:

public func createPDFDirectly(text: NSAttributedString, documentPath: URL) {
    // A4页面及边距设置
    let pageSize = CGSize(width: 595.2, height: 841.8)
    let pageMargins = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)
    let printableSize = CGSize(width: pageSize.width - pageMargins.left - pageMargins.right, 
                               height: pageSize.height - pageMargins.top - pageMargins.bottom)
    
    // 初始化CoreText排版组件
    let textStorage = NSTextStorage(attributedString: text)
    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: printableSize)
    
    textStorage.addLayoutManager(layoutManager)
    layoutManager.addTextContainer(textContainer)
    
    let pageCount = layoutManager.numberOfPages(for: textContainer)
    let pdfData = NSMutableData()
    
    // 开始PDF上下文
    UIGraphicsBeginPDFContextToData(pdfData, CGRect(origin: .zero, size: pageSize), nil)
    
    for pageIndex in 0..<pageCount {
        UIGraphicsBeginPDFPage()
        
        // 将坐标系平移到边距位置
        UIGraphicsGetCurrentContext()?.translateBy(x: pageMargins.left, y: pageMargins.top)
        
        // 绘制当前页面的富文本内容
        let glyphRange = layoutManager.glyphRange(forPageAt: pageIndex, in: textContainer)
        layoutManager.drawGlyphs(forGlyphRange: glyphRange, at: .zero)
    }
    
    UIGraphicsEndPDFContext()
    
    // 写入文件
    do {
        try pdfData.write(to: documentPath)
    } catch {
        print(error.localizedDescription)
    }
}

额外检查点

  • 确认你的NSTextAttachment的image不为nil:比如UIImage(named: "1.png")要确保图片存在于当前target的bundle中,或者用UIImage(contentsOfFile:)加载绝对路径的图片
  • 如果你要转换的是UITextView的内容,直接传入textView.attributedText即可,不需要依赖viewPrintFormatter()

内容的提问来源于stack exchange,提问作者Valerio Santinelli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:30:09