NSTextAttachmentはUIKitフレームワークのクラスで、NSAttributedStringを介してフォーマットされたテキストに画像やその他のメディアオブジェクトを埋め込むことができます。TextKitおよびCore Textレベルで動作し、開発者はテキストフロー内でのアタッチメントのサイズ、位置、動作を制御できます。Apple Documentation(2025)によると、NSTextAttachmentは画像、PDF、カスタムビューコンテナをサポートし、UITextViewおよびUILabelと統合します。iOSアプリケーションでリッチテキストを作成するには、このAPIを理解することが不可欠です。チャットやエディターから、キャプションにアイコンがあるニュースフィードまで対応します。
重要なポイント
NSTextAttachmentはUIKitフレームワークのクラスで、メディアオブジェクトをNSAttributedStringのテキストコンテンツに直接埋め込むことができます。テキストの横に配置される別個のUIImageViewとは異なり、NSTextAttachmentは画像をテキストフローの一部にします。画像は行とともに折り返され、配置に参加し、通常の文字のようにスペースを占有します。
このクラスはTextKitアーキテクチャの一部であり、iOSおよびmacOSでのテキストレンダリングを管理します。TextKitはテキストをグリフに分割し、カーニング、リガチャ、リーディングを考慮します。NSTextAttachmentはこのパイプラインに特殊文字として適合し、文字としてではなくグラフィックオブジェクトとしてレンダリングされます。
各NSTextAttachmentインスタンスには、contents(ファイルデータ)、fileType(UTIコンテンツタイプ)、bounds(サイズと位置を定義するポイント単位の矩形)が含まれます。Apple Human Interface Guidelinesによると、boundsはデフォルトで画像サイズですが、フォントに正確に合わせるために変更できます。
画像がテキストの一部として動作する必要がある場合(チャット、ニュースフィード、テキストエディター、入力フィールドにアイコンがあるフォームなど)は、個別のUIImageViewではなくNSTextAttachmentを使用してください。
NSTextAttachmentの基本的なワークフローは3つのステップで構成されます:インスタンスを作成し、画像を設定し、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プロパティの画像がその位置に描画されます。
画像をテキスト行の途中に配置する場合(たとえば、単語の後のアイコン)、NSMutableAttributedStringの目的の位置にattachmentStringを挿入するだけです。TextKitは自動的に行の高さを考慮し、ベースラインに画像を配置します。
重要:imageプロパティはiOSでのみ利用可能です(iOS 7以降)。macOSでは、NSImageとともにcontentsプロパティを使用してください。下位互換性のために、データコンテナに依存するよりも常に直接imageを設定することを推奨します。
デフォルトでは、NSTextAttachmentは画像をポイント単位の元のサイズで表示します。実際には、サイズと垂直位置をほぼ常に調整する必要があります。これには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画像を設定すると、システムは自動的にドキュメントの最初のページをレンダリングします。ただし、正確なレンダリングのためには、データと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文字列(Uniform Type Identifier)を受け入れます。標準タイプには、public.image(任意の画像)、public.jpeg、public.png、com.adobe.pdfが含まれます。正しいUTIを指定すると、システムは適切なレンダリング方法を選択します。PDFの場合はCGPDFDocument、画像の場合はCGImageSourceです。
カスタムファイルタイプ(たとえば、SVG形式のベクターグラフィック)の場合、image(forBounds:textContainer:characterIndex:)メソッドをオーバーライドするNSTextAttachmentのサブクラスが必要です。標準のレンダリングが適切でない場合は、nilを返し、drawメソッドでCore Graphicsを介した描画を実装します。
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を使用します。
1つ目の一般的なエラーはboundsの無視です。開発者は元の画像サイズに依存しがちで、テキスト内に巨大なアイコンが表示されたり、逆にほとんど見えない画像になったりします。フォントとコンテキストを考慮して、常にboundsを明示的に設定してください。
2つ目のエラーはYオフセットの誤りです。bounds.origin.yの正の値は画像を下げます(UIKitの座標系ではY軸が下向きです。直感に反しますが、Core Graphicsの動作です)。行内で中央揃えにするには、セクション3で示したように、フォントのcapHeightを使用した式を使用します。
3つ目の問題はtraitCollection変更時の画像消失です。ユーザーがダークモードやDynamic Typeを切り替えると、フォントサイズが変更される可能性がありますが、boundsは同じままです。解決策は、layoutSubviewsメソッドまたはフォントのKVOを介してboundsを動的に計算することです。
4つ目の頻繁なエラーは、numberOfLines > 1のUILabelでのNSTextAttachmentの使用です。制限された幅のマルチラインモードでは、TextKitは画像とともに行を正しく折り返します。問題は、アタッチメントの高さが行の高さを超えると発生し、隣接する行が互いに重なります。解決策は、NSMutableParagraphStyleを介してlineSpacingを増やすことです。
よくある質問
NSTextAttachmentは画像をテキストフローの一部にします。テキストとともに折り返され、配置され、スケーリングされます。UIImageViewはスーパービューの座標にバインドされ、テキストレイアウトには関与しません。
はい、textの代わりにattributedTextプロパティを設定すれば可能です。UILabelはNSTextAttachmentを正しく表示しますが、インタラクティビティはサポートしません。アタッチメントのタップにはUITextViewを使用してください。
既存のNSTextAttachmentインスタンスのboundsプロパティを変更します。新しいNSAttributedStringを作成する必要なく、テキストは自動的に新しいサイズで再レンダリングされます。
NSTextAttachmentは静止画像をサポートします。GIFやアニメーション形式の場合は、iOS 15+のNSTextAttachmentViewProviderまたはCADisplayLinkを介したカスタム実装が必要です。
NSTextAttachmentインスタンスを作成し、画像またはデータを設定し、boundsを設定し、NSAttributedString(attachment:)でラップし、insert(_:at:)メソッドを使用してNSMutableAttributedStringに挿入します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。