Το NSTextAttachment είναι μια κλάση από το πλαίσιο UIKit που επιτρέπει την ενσωμάτωση εικόνων και άλλων αντικειμένων πολυμέσων σε μορφοποιημένο κείμενο μέσω NSAttributedString. Λειτουργεί σε επίπεδο TextKit και Core Text, δίνοντας στους προγραμματιστές έλεγχο πάνω στο μέγεθος, τη θέση και τη συμπεριφορά των συνημμένων μέσα στη ροή κειμένου. Σύμφωνα με το Apple Documentation (2025), το NSTextAttachment υποστηρίζει εικόνες, PDF και προσαρμοσμένα κοντέινερ προβολής, ενσωματώνοντας με UITextView και UILabel. Η κατανόηση αυτού του API είναι απαραίτητη για τη δημιουργία Rich Text σε εφαρμογές iOS — από συνομιλίες και επεξεργαστές έως ροές ειδήσεων με εικονίδια σε λεζάντες.
Κύρια σημεία
NSTextAttachment είναι μια κλάση από το πλαίσιο UIKit που επιτρέπει την ενσωμάτωση αντικειμένων πολυμέσων απευθείας στο περιεχόμενο κειμένου του NSAttributedString. Σε αντίθεση με ένα ξεχωριστό UIImageView τοποθετημένο δίπλα στο κείμενο, το NSTextAttachment καθιστά την εικόνα μέρος της ροής κειμένου: η εικόνα μετακινείται μαζί με τις γραμμές, συμμετέχει στη στοίχιση και καταλαμβάνει χώρο σαν ένα συνηθισμένο σύμβολο.
Η κλάση αποτελεί μέρος της αρχιτεκτονικής TextKit που διαχειρίζεται την απόδοση κειμένου σε iOS και macOS. Το TextKit χωρίζει το κείμενο σε γλύφους, λαμβάνει υπόψη kerning, ligatures και διάστιχο, και το 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, το οποίο προσαρτάται σε έναν ειδικό χαρακτήρα αντικατάστασης (object replacement character, κωδικός 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 της γραμματοσειράς, το ύψος του κεφαλαίου γράμματος, όχι το πλήρες ύψος γραμμής. Αυτό εγγυάται ότι το εικονίδιο ταιριάζει οπτικά με το άνω όριο του κειμένου και δεν αιωρείται μεταξύ baseline και ascender. Το μέγεθος εικόνας στο παράδειγμα είναι 18×18 pt, τυπικό για εικονίδια σε κείμενο UI.
Εάν η εικόνα πρέπει να είναι μεγαλύτερη από τη γραμμή κειμένου (π.χ. προεπισκόπηση φωτογραφίας σε συνομιλία), το TextKit αυξάνει αυτόματα το διάστιχο για την τρέχουσα γραμμή. Σε αυτήν την περίπτωση, το bounds ορίζεται σε φυσικό μέγεθος και η μετατόπιση Y είναι μηδέν.
Το NSTextAttachment υποστηρίζει όχι μόνο PNG και JPEG, αλλά και έγγραφα PDF καθώς και αυθαίρετα αρχεία μέσω της ιδιότητας contents. Αυτό το καθιστά ένα καθολικό εργαλείο για Rich Text σε εφαρμογές iOS.
Κατά τον ορισμό εικόνας PDF στο attachment.image, το σύστημα αποδίδει αυτόματα την πρώτη σελίδα του εγγράφου. Ωστόσο, για ακριβή εμφάνιση, χρησιμοποιήστε τα δεδομένα απευθείας μέσω του αρχικοποιητή με 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 (Uniform Type Identifier). Τυπικοί τύποι: 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.
Για τον χειρισμό πατήματος στο NSTextAttachment σε UITextView, χρησιμοποιείται ο εκπρόσωπος UITextViewDelegate και η μέθοδος textView(_:shouldInteractWith:in:interaction:). Αυτή η μέθοδος καλείται όταν ο χρήστης πατάει το συνημμένο και επιτρέπει την πλοήγηση σε οθόνη, το άνοιγμα popup ή την αναπαραγωγή κινούμενων γραφικών.
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.
Κατά την επεξεργασία UITextView με NSTextAttachment, είναι σημαντικό να θυμάστε: η διαγραφή του χαρακτήρα αντικατάστασης (0xFFFC) διαγράφει και το συνημμένο. Ο χρήστης βλέπει την εικόνα ως ένα ενιαίο στοιχείο, επιλέγεται ολόκληρη, όχι pixel προς pixel. Για υποστήριξη μεταφοράς συνημμένων σε iOS 15+, χρησιμοποιήστε το NSTextAttachmentViewProvider.
Το πρώτο συνηθισμένο λάθος — αγνόηση του bounds. Οι προγραμματιστές συχνά βασίζονται στο αρχικό μέγεθος της εικόνας, οδηγώντας σε γιγαντιαία εικονίδια στο κείμενο ή, αντιθέτως, σε εικόνες που μόλις διακρίνονται. Πάντα ορίζετε το bounds ρητά, λαμβάνοντας υπόψη τη γραμματοσειρά και το περιβάλλον.
Το δεύτερο λάθος — λανθασμένη μετατόπιση Y. Μια θετική τιμή στο bounds.origin.y κατεβάζει την εικόνα (στο σύστημα συντεταγμένων UIKit ο άξονας Y δείχνει προς τα κάτω, αυτό φαίνεται παράλογο, αλλά έτσι λειτουργεί το Core Graphics). Για στοίχιση στο κέντρο της γραμμής, χρησιμοποιήστε τον τύπο με το capHeight της γραμματοσειράς, όπως φαίνεται στην ενότητα 3.
Το τρίτο πρόβλημα — απώλεια εικόνας κατά την αλλαγή traitCollection. Όταν ο χρήστης αλλάζει Dark Mode ή Dynamic Type, το μέγεθος γραμματοσειράς μπορεί να αλλάξει, ενώ το bounds παραμένει ίδιο. Λύση, υπολογίστε το bounds δυναμικά στη μέθοδο layoutSubviews ή μέσω KVO στο font.
Το τέταρτο συνηθισμένο λάθος — χρήση NSTextAttachment σε UILabel με numberOfLines > 1. Σε λειτουργία πολλαπλών γραμμών και περιορισμένου πλάτους, το TextKit μεταφέρει σωστά τις γραμμές μαζί με την εικόνα. Το πρόβλημα προκύπτει όταν το ύψος του συνημμένου υπερβαίνει το ύψος της γραμμής, οι γειτονικές γραμμές επικαλύπτονται. Λύση, αυξήστε το lineSpacing μέσω NSMutableParagraphStyle.
Συχνές ερωτήσεις
NSTextAttachment καθιστά την εικόνα μέρος της ροής κειμένου: μετακινείται, στοιχίζεται και κλιμακώνεται μαζί με το κείμενο. Το UIImageView είναι συνδεδεμένο με τις συντεταγμένες του superview και δεν συμμετέχει στη διάταξη κειμένου.
Ναι, εάν ορίσετε την ιδιότητα attributedText αντί για text. Το UILabel εμφανίζει σωστά το NSTextAttachment, αλλά δεν υποστηρίζει διαδραστικότητα. Για πάτημα στο συνημμένο χρησιμοποιήστε UITextView.
Αλλάξτε την ιδιότητα bounds του υπάρχοντος στιγμιοτύπου NSTextAttachment. Το κείμενο θα αναποδοθεί αυτόματα με τα νέα μεγέθη χωρίς να χρειαστεί να δημιουργήσετε νέο NSAttributedString.
NSTextAttachment υποστηρίζει στατικές εικόνες. Για GIF και κινούμενες μορφές απαιτείται προσαρμοσμένη υλοποίηση μέσω NSTextAttachmentViewProvider σε iOS 15+ ή μέσω CADisplayLink.
Δημιουργήστε ένα στιγμιότυπο NSTextAttachment, ορίστε image ή data, καθορίστε bounds, τυλίξτε σε NSAttributedString(attachment:) και εισάγετε στο NSMutableAttributedString μέσω της μεθόδου insert(_:at:).
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης