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

如何使UITextView中NSTextAttachment的图片尺寸在App重启后仍保持40×40

解决UITextView图片重启后尺寸丢失的问题

这个问题我之前也碰到过,核心原因是默认的NSAttributedStringTransformer没有完整保存NSTextAttachment的bounds属性——当你把attributedText存到CoreData再重启App加载时,attachment的尺寸信息丢失了,所以图片会用原始大小渲染,自然就超出屏幕了。

下面给你两种可行的解决思路:

思路一:加载时重新设置所有图片的尺寸

这个方法最简单,适合快速修复。在你从CoreData读取并设置textView的attributedText之后,遍历所有文本附件,强制把bounds改回40×40:

// 假设你从CoreData取出了savedAttributedText
let mutableAttributedText = NSMutableAttributedString(attributedString: savedAttributedText)
let range = NSRange(location: 0, length: mutableAttributedText.length)

mutableAttributedText.enumerateAttribute(.attachment, in: range, options: []) { value, range, stop in
    guard let attachment = value as? NSTextAttachment else { return }
    // 重新设置固定尺寸
    attachment.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
}

textView.attributedText = mutableAttributedText

优点是代码改动小,不需要修改CoreData的Transformer配置;缺点是如果以后要支持不同尺寸的图片,这个方法就不适用了。

思路二:自定义Transformer保存完整的Attachment信息

如果需要支持不同尺寸的图片,或者想从根源解决问题,可以自定义一个Transformer,让它序列化NSTextAttachment时同时保存bounds和image数据:

步骤1:创建自定义Transformer

import CoreData
import UIKit

class CustomAttributedStringTransformer: ValueTransformer {
    override class func transformedValueClass() -> AnyClass {
        return NSAttributedString.self
    }
    
    override class func allowsReverseTransformation() -> Bool {
        return true
    }
    
    override func transformedValue(_ value: Any?) -> Any? {
        guard let attributedText = value as? NSAttributedString else { return nil }
        // 用NSKeyedArchiver序列化,处理NSTextAttachment的自定义属性
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: attributedText, requiringSecureCoding: false)
            return data
        } catch {
            print("Failed to archive attributed text: \(error)")
            return nil
        }
    }
    
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }
        do {
            let attributedText = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSAttributedString.self, from: data)
            return attributedText
        } catch {
            print("Failed to unarchive attributed text: \(error)")
            return nil
        }
    }
}

// 注册Transformer
extension ValueTransformer {
    static let customAttributedStringTransformer = CustomAttributedStringTransformer()
}

步骤2:在CoreData模型中配置Transformer

打开你的CoreData模型文件,找到对应的Transformable类型属性,在Inspector面板的"Transformer"字段中输入customAttributedStringTransformer(即你注册的Transformer名称)。

额外处理:让NSTextAttachment支持完整归档

默认的NSTextAttachment不会自动归档bounds,我们给它加个扩展实现NSSecureCoding:

extension NSTextAttachment: NSSecureCoding {
    static var supportsSecureCoding: Bool {
        return true
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(image, forKey: "image")
        coder.encode(bounds, forKey: "bounds")
    }
    
    required init?(coder: NSCoder) {
        super.init(data: nil, ofType: nil)
        self.image = coder.decodeObject(forKey: "image") as? UIImage
        self.bounds = coder.decodeCGRect(forKey: "bounds")
    }
}

这样归档NSAttributedString时,NSTextAttachment的bounds和image都会被完整保存,重启App加载后尺寸就不会丢失了。

小提示(iOS 15+适用)

如果你项目的最低版本是iOS 15+,可以用NSAttributedString.DocumentType.rtfd格式保存文本和附件,RTFD会完整保留所有格式信息,包括图片尺寸:

// 保存时
if let rtfdData = textView.attributedText?.rtfdFileWrapper(from: NSRange(location: 0, length: textView.attributedText.length), documentAttributes: [:])?.regularFileContents {
    // 把rtfdData存到CoreData
}

// 加载时
if let rtfdData = savedRtfdData, let fileWrapper = FileWrapper(regularFileWithContents: rtfdData), let attributedText = NSAttributedString(rtfdFileWrapper: fileWrapper, documentAttributes: nil) {
    textView.attributedText = attributedText
}

这个方法不需要自定义Transformer,操作更简洁,适合高版本iOS项目。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 00:08:10