NSTextAttachment 是 UIKit 框架中的一个类,允许通过 NSAttributedString 将图像和其他媒体对象嵌入到格式化文本中。它在 TextKit 和 Core Text 级别工作,使开发人员能够控制文本流中附件的大小、位置和行为。根据 Apple Documentation (2025),NSTextAttachment 支持图像、PDF 和自定义视图容器,并与 UITextView 和 UILabel 集成。理解这个 API 对于在 iOS 应用中创建富文本至关重要,从聊天和编辑器到带有图标说明的新闻源。
要点
NSTextAttachment 是 UIKit 框架中的一个类,允许将媒体对象直接嵌入到 NSAttributedString 的文本内容中。与放置在文本旁边的独立 UIImageView 不同,NSTextAttachment 使图像成为文本流的一部分:图像随行移动,参与对齐,并像普通字符一样占据空间。
该类是 TextKit 架构的一部分,该架构管理 iOS 和 macOS 上的文本渲染。TextKit 将文本分解为字形,考虑字距、连字和行距,而 NSTextAttachment 作为特殊字符加入此管道,渲染为图形对象而不是字母。
每个 NSTextAttachment 实例包含 contents(文件数据)、fileType(内容的 UTI 类型)和 bounds(以 points 为单位定义大小和位置的矩形)属性。根据 Apple Human Interface Guidelines,bounds 默认为图像大小,但可以更改以精确匹配字体。
当图像应该作为文本的一部分时,使用 NSTextAttachment 而不是单独的 UIImageView,在聊天、新闻源、文本编辑器和输入字段中带有图标的表单中。
使用 NSTextAttachment 的基本场景包括三个步骤:创建实例、设置图像和附加到 NSAttributedString。让我们通过 Swift 示例来看这个过程。
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "icon-star")
let attachmentString = NSAttributedString(attachment: attachment)
let text = NSMutableAttributedString(string: "Rating: ")
text.append(attachmentString)
let label = UILabel()
label.attributedText = text
调用 NSAttributedString(attachment:) 后,NSTextAttachment 对象转换为 NSAttachmentAttributeName 属性,该属性附加到一个特殊的替换字符(对象替换字符,代码 0xFFFC)上。该字符不显示为字母,而是绘制来自 image 属性的图像。
如果图像应位于文本行的中间(例如单词后的图标),只需将 attachmentString 插入到 NSMutableAttributedString 中的所需位置。TextKit 将自动考虑行高并将图像对齐到基线。
重要提示:image 属性仅在 iOS 上可用(从 iOS 7 开始)。在 macOS 上使用 contents 属性和 NSImage。为了向后兼容,始终直接设置 image,不要依赖数据容器。
默认情况下,NSTextAttachment 以原始大小(以 points 为单位)显示图像。在实践中,几乎总是需要调整大小和垂直位置,为此使用 bounds 属性。
CGRect bounds 结构包括 origin(X 和 Y 上的偏移)和 size(宽度和高度)。Y 上的偏移尤其重要:负值将图像置于基线下方,正值将其抬高。典型场景是将图标对齐到文本行的中心。
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "icon-star")
let font = UIFont.systemFont(ofSize: 16)
let fontCapHeight = font.capHeight
let imageSize = CGSize(width: 18, height: 18)
attachment.bounds = CGRect(
x: 0,
y: (fontCapHeight - imageSize.height) / 2,
width: imageSize.width,
height: imageSize.height
)
对齐计算使用字体的 capHeight(大写字母的高度,而不是完整的行高)。这确保图标在视觉上与文本的上边界对齐,不会在基线和上升部之间晃动。示例中的图像大小为 18×18 pt,这是 UI 文本中图标的典型尺寸。
如果图像应大于文本行(例如聊天中的照片预览),TextKit 将自动增加当前行的行间距。在这种情况下,bounds 设置为自然大小,Y 偏移为零。
NSTextAttachment 不仅支持 PNG 和 JPEG,还支持 PDF 文档以及通过 contents 属性支持任意文件。这使其成为 iOS 应用中富文本的通用工具。
在 attachment.image 中设置 PDF 图像时,系统会自动渲染文档的第一页。但是,要精确显示,请通过使用 data 和 UTI 类型的初始化器直接使用数据:
guard let pdfData = Bundle.main.url(forResource: "document",
withExtension: "pdf") else { return }
let fileData = Data(contentsOf: pdfData)
let attachment = NSTextAttachment(data: fileData,
ofType: "com.adobe.pdf")
attachment.bounds = CGRect(x: 0, y: -4,
width: 24, height: 24)
let pdfString = NSAttributedString(attachment: attachment)
ofType 参数接受 UTI 字符串(统一类型标识符)。标准类型:public.image(任何图像)、public.jpeg、public.png、com.adobe.pdf。在指定正确的 UTI 时,系统会选择适当的渲染方法,对于 PDF 是 CGPDFDocument,对于图像是 CGImageSource。
对于自定义文件类型(例如 SVG 格式的矢量图形),需要 NSTextAttachment 的子类来重写 image(forBounds:textContainer:characterIndex:) 方法。如果标准渲染不合适,返回 nil 并通过 Core Graphics 在 draw 方法中实现绘制。
NSTextAttachment 在 UILabel 和 UITextView 中都能正常工作。然而,UITextView 提供了更多功能:与附件交互(点击图标)、编辑文本以及图像和支持 NSAttachmentBehavior。
为了处理 UITextView 中 NSTextAttachment 的点击,使用 UITextViewDelegate 委托和 textView(_:shouldInteractWith:in:interaction:) 方法。当用户点击附件时调用此方法,允许导航到屏幕、打开弹出窗口或播放动画。
func textView(_ textView: UITextView,
shouldInteractWith attachment: NSTextAttachment,
in characterRange: NSRange,
interaction: UITextItemInteraction) -> Bool {
if interaction == .preview {
return false
}
// 打开详情屏幕
showImageDetail(attachment.image)
return false
}
该方法通过 interaction 参数区分交互类型:.preview(3D Touch / Haptic Touch)、.default(普通点击)和 .presentActions(上下文菜单)。对于每种类型,您可以设置自己的行为或通过返回 false 禁用它。
在编辑带有 NSTextAttachment 的 UITextView 时,重要的是要记住:删除替换字符(0xFFFC)也会删除附件。用户将图像视为一个整体元素,整体选中,而不是逐像素。为了在 iOS 15+ 中支持附件的拖放,使用 NSTextAttachmentViewProvider。
第一个常见错误 — 忽略 bounds。开发人员经常依赖图像的原始大小,导致文本中出现巨大的图标,或者相反,几乎看不见的图像。始终显式设置 bounds,考虑字体和上下文。
第二个错误 — Y 偏移不正确。bounds.origin.y 中的正值会将图像向下移动(在 UIKit 坐标系中,Y 轴向下,这似乎不合逻辑,但 Core Graphics 就是这样工作的)。要居中到行的中心,使用字体的 capHeight 公式,如第 3 节所示。
第三个问题 — 在 traitCollection 改变时丢失图像。当用户切换深色模式或动态类型时,字体大小可能会改变,而 bounds 保持不变。解决方案 — 在 layoutSubviews 方法中或通过 font 上的 KVO 动态计算 bounds。
第四个常见错误 — 在 UILabel 中使用 numberOfLines > 1 的 NSTextAttachment。在多行模式和有限宽度下,TextKit 正确地将行与图像一起移动。当附件高度超过行高时出现问题,相邻行会重叠。解决方案 — 通过 NSMutableParagraphStyle 增加 lineSpacing。
常见问题
NSTextAttachment 使图像成为文本流的一部分:它随文本移动、对齐和缩放。UIImageView 绑定到父视图的坐标,不参与文本布局。
可以,如果设置 attributedText 属性而不是 text。UILabel 正确显示 NSTextAttachment,但不支持交互性。点击附件使用 UITextView。
更改现有 NSTextAttachment 实例的 bounds 属性。文本将自动使用新大小重新渲染,无需创建新的 NSAttributedString。
NSTextAttachment 支持静态图像。对于 GIF 和动画格式,需要通过 NSTextAttachmentViewProvider(在 iOS 15+ 中)或通过 CADisplayLink 进行自定义实现。
创建 NSTextAttachment 实例,设置 image 或 data,设置 bounds,包装到 NSAttributedString(attachment:) 中,并通过 insert(_:at:) 方法插入到 NSMutableAttributedString 中。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。