NSTextAttachment là một lớp từ framework UIKit cho phép nhúng hình ảnh và các đối tượng đa phương tiện khác vào văn bản được định dạng thông qua NSAttributedString. Nó hoạt động ở cấp độ TextKit và Core Text, cho phép nhà phát triển kiểm soát kích thước, vị trí và hành vi của các tệp đính kèm trong luồng văn bản. Theo Apple Documentation (2025), NSTextAttachment hỗ trợ hình ảnh, PDF và các container view tùy chỉnh, tích hợp với UITextView và UILabel. Hiểu API này là cần thiết để tạo Rich Text trong các ứng dụng iOS — từ chat và trình soạn thảo đến nguồn cấp tin tức với biểu tượng trong chú thích.
Các điểm chính
NSTextAttachment là một lớp từ framework UIKit cho phép nhúng các đối tượng đa phương tiện trực tiếp vào nội dung văn bản của NSAttributedString. Không giống như UIImageView riêng biệt được đặt cạnh văn bản, NSTextAttachment làm cho hình ảnh trở thành một phần của luồng văn bản: hình ảnh ngắt dòng cùng với các dòng, tham gia căn chỉnh và chiếm không gian như một ký tự thông thường.
Lớp này là một phần của kiến trúc TextKit, quản lý việc hiển thị văn bản trên iOS và macOS. TextKit chia văn bản thành các glyph, tính đến kerning, ligature và leading — và NSTextAttachment phù hợp vào đường ống này như một ký tự đặc biệt được hiển thị không phải như một chữ cái mà như một đối tượng đồ họa.
Mỗi phiên bản NSTextAttachment chứa contents (dữ liệu tệp), fileType (loại nội dung UTI) và bounds (một hình chữ nhật tính bằng points xác định kích thước và vị trí). Theo Apple Human Interface Guidelines, bounds mặc định là kích thước hình ảnh nhưng có thể thay đổi để khớp chính xác với phông chữ.
Sử dụng NSTextAttachment thay vì UIImageView riêng biệt khi hình ảnh cần hoạt động như một phần của văn bản — trong chat, nguồn cấp tin tức, trình soạn thảo văn bản và biểu mẫu có biểu tượng trong trường nhập.
Quy trình cơ bản với NSTextAttachment bao gồm ba bước: tạo một phiên bản, đặt hình ảnh và gắn nó vào NSAttributedString. Hãy xem quy trình sử dụng 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
Sau khi gọi NSAttributedString(attachment:), đối tượng NSTextAttachment được chuyển đổi thành thuộc tính NSAttachmentAttributeName, được gắn vào một ký tự thay thế đối tượng đặc biệt (mã 0xFFFC). Ký tự này không được hiển thị dưới dạng chữ cái — thay vào đó, hình ảnh từ thuộc tính image được vẽ ở vị trí của nó.
Nếu hình ảnh cần được đặt ở giữa một dòng văn bản (ví dụ, biểu tượng sau một từ), chỉ cần chèn attachmentString vào vị trí mong muốn trong NSMutableAttributedString. TextKit sẽ tự động tính đến chiều cao dòng và căn chỉnh hình ảnh theo đường cơ sở.
Quan trọng: thuộc tính image chỉ khả dụng trên iOS (từ iOS 7). Trên macOS, sử dụng thuộc tính contents với NSImage. Để tương thích ngược, luôn ưu tiên đặt image trực tiếp thay vì dựa vào container dữ liệu.
Theo mặc định, NSTextAttachment hiển thị hình ảnh ở kích thước gốc tính bằng points. Trong thực tế, hầu như luôn cần điều chỉnh kích thước và vị trí theo chiều dọc — điều này được thực hiện thông qua thuộc tính bounds.
Cấu trúc CGRect bounds bao gồm origin (độ dịch chuyển theo X và Y) và size (chiều rộng và chiều cao). Độ dịch chuyển Y đặc biệt quan trọng: giá trị âm đẩy hình ảnh xuống dưới đường cơ sở, giá trị dương nâng nó lên. Một tình huống điển hình là căn chỉnh biểu tượng vào giữa dòng văn bản.
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
)
Tính toán căn chỉnh sử dụng capHeight của phông chữ — chiều cao của một chữ in hoa, không phải chiều cao toàn dòng. Điều này đảm bảo biểu tượng khớp trực quan với mép trên của văn bản thay vì trôi nổi giữa đường cơ sở và đường lên. Kích thước hình ảnh trong ví dụ là 18×18 pt, điển hình cho các biểu tượng trong văn bản UI.
Nếu hình ảnh nên lớn hơn dòng văn bản (ví dụ, xem trước ảnh trong chat), TextKit sẽ tự động tăng khoảng cách dòng cho dòng hiện tại. Trong trường hợp này, bounds nên được đặt ở kích thước tự nhiên và độ dịch chuyển Y bằng không.
NSTextAttachment không chỉ hỗ trợ PNG và JPEG mà còn cả tài liệu PDF, cũng như các tệp tùy ý thông qua thuộc tính contents. Điều này làm cho nó trở thành một công cụ phổ quát cho Rich Text trong các ứng dụng iOS.
Khi đặt hình ảnh PDF vào attachment.image, hệ thống tự động hiển thị trang đầu tiên của tài liệu. Tuy nhiên, để hiển thị chính xác, hãy sử dụng dữ liệu trực tiếp thông qua bộ khởi tạo với dữ liệu và loại 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)
Tham số ofType chấp nhận một chuỗi UTI (Uniform Type Identifier). Các loại tiêu chuẩn bao gồm: public.image (bất kỳ hình ảnh nào), public.jpeg, public.png, com.adobe.pdf. Khi chỉ định UTI chính xác, hệ thống sẽ chọn phương thức hiển thị phù hợp — đối với PDF là CGPDFDocument, đối với hình ảnh là CGImageSource.
Đối với các loại tệp tùy chỉnh (ví dụ, đồ họa vector định dạng SVG), bạn sẽ cần một lớp con của NSTextAttachment ghi đè phương thức image(forBounds:textContainer:characterIndex:). Nếu hiển thị tiêu chuẩn không phù hợp, hãy trả về nil và triển khai vẽ qua Core Graphics trong phương thức draw.
NSTextAttachment hoạt động chính xác trong cả UILabel và UITextView. Tuy nhiên, UITextView cung cấp nhiều khả năng hơn: tương tác với các tệp đính kèm (chạm vào biểu tượng), chỉnh sửa văn bản cùng với hình ảnh và hỗ trợ NSAttachmentBehavior.
Để xử lý thao tác chạm trên NSTextAttachment trong UITextView, hãy sử dụng giao thức UITextViewDelegate và phương thức textView(_:shouldInteractWith:in:interaction:). Phương thức này được gọi khi người dùng chạm vào một tệp đính kèm và cho phép điều hướng đến một màn hình, mở cửa sổ bật lên hoặc phát hoạt ảnh.
func textView(_ textView: UITextView,
shouldInteractWith attachment: NSTextAttachment,
in characterRange: NSRange,
interaction: UITextItemInteraction) -> Bool {
if interaction == .preview {
return false
}
// Mở màn hình chi tiết
showImageDetail(attachment.image)
return false
}
Phương thức phân biệt các loại tương tác thông qua tham số interaction: .preview (3D Touch / Haptic Touch), .default (chạm thông thường) và .presentActions (menu ngữ cảnh). Đối với mỗi loại, bạn có thể xác định hành vi riêng hoặc vô hiệu hóa nó bằng cách trả về false.
Khi chỉnh sửa UITextView với NSTextAttachment, điều quan trọng cần nhớ là: xóa ký tự thay thế (0xFFFC) cũng xóa tệp đính kèm. Người dùng thấy hình ảnh như một phần tử duy nhất — nó được chọn toàn bộ, không phải từng pixel. Để hỗ trợ kéo và thả các tệp đính kèm trên iOS 15+, hãy sử dụng NSTextAttachmentViewProvider.
Lỗi phổ biến đầu tiên là bỏ qua bounds. Các nhà phát triển thường dựa vào kích thước hình ảnh gốc, dẫn đến các biểu tượng khổng lồ trong văn bản hoặc ngược lại, hình ảnh hầu như không nhìn thấy. Luôn đặt bounds một cách rõ ràng, có tính đến phông chữ và ngữ cảnh.
Lỗi thứ hai là độ dịch chuyển Y không chính xác. Giá trị dương trong bounds.origin.y đẩy hình ảnh xuống (trong hệ tọa độ UIKit, trục Y hướng xuống dưới — điều này có vẻ phản trực giác, nhưng Core Graphics hoạt động như vậy). Để căn giữa trong một dòng, hãy sử dụng công thức với capHeight của phông chữ như được trình bày trong Phần 3.
Vấn đề thứ ba là mất hình ảnh khi thay đổi traitCollection. Khi người dùng chuyển đổi Chế độ tối hoặc Dynamic Type, kích thước phông chữ có thể thay đổi nhưng bounds vẫn giữ nguyên. Giải pháp là tính toán bounds một cách động trong phương thức layoutSubviews hoặc thông qua KVO trên font.
Lỗi thường gặp thứ tư là sử dụng NSTextAttachment trong UILabel với numberOfLines > 1. Ở chế độ nhiều dòng với chiều rộng hạn chế, TextKit ngắt dòng chính xác cùng với hình ảnh. Vấn đề phát sinh khi chiều cao của tệp đính kèm vượt quá chiều cao dòng — các dòng liền kề chồng lên nhau. Giải pháp là tăng lineSpacing thông qua NSMutableParagraphStyle.
Câu hỏi thường gặp
NSTextAttachment làm cho hình ảnh trở thành một phần của luồng văn bản: nó ngắt dòng, căn chỉnh và tỷ lệ cùng với văn bản. UIImageView bị ràng buộc vào tọa độ superview và không tham gia vào bố cục văn bản.
Có, nếu bạn đặt thuộc tính attributedText thay vì text. UILabel hiển thị NSTextAttachment chính xác nhưng không hỗ trợ tương tác. Để chạm vào tệp đính kèm, hãy sử dụng UITextView.
Thay đổi thuộc tính bounds của phiên bản NSTextAttachment hiện có. Văn bản sẽ tự động hiển thị lại với kích thước mới mà không cần tạo NSAttributedString mới.
NSTextAttachment hỗ trợ hình ảnh tĩnh. Đối với GIF và các định dạng động, cần có triển khai tùy chỉnh thông qua NSTextAttachmentViewProvider trên iOS 15+ hoặc qua CADisplayLink.
Tạo một phiên bản NSTextAttachment, đặt hình ảnh hoặc dữ liệu, đặt bounds, bọc nó trong NSAttributedString(attachment:) và chèn vào NSMutableAttributedString bằng phương thức insert(_:at:).
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm