NSTextAttachment in MacOS vs iOS

Today I learned another difference between iOS and MacOS: Dealing with attachments (specifically, images) in rich text. This is something I’m adding for my Permanent Marker project, which is a multi-platform UIKit / AppKit application.

I had my first exposure working with attachments when working on my Library Notes application. Library Notes does not have full rich text editing, but it does support embedded images, and that’s where I learned the following simple recipe for adding an image to rich text on iOS:

  1. Create an NSTextAttachment that contains the data for your image.
  2. Create an NSAttributedString that displays the attachment and insert it into the appropriate location in the rest of your rich text. (Rich text attachments are stored as an attribute on a NSTextAttachment.character. While you can manually create this by inserting the character and applying the right attribute, it’s easier to use the NSAttributedString(attachment:) initializer.)

Now often I find myself trying to insert photographs into text, which presents a problem: Modern digital photos (even ones from cell phone cameras) are huge. Consequently, I’m almost never working with raw image data from disk or the network for my attachments; instead, I use CoreImage to resize the image before including it as an attachment. This works fine on iOS; NSTextAttachment has an image property that I can use for my resized image.

Things are a little different on MacOS. First, and a little surprisingly to me, the designated initializer for NSTextAttachment on the Mac takes a FileWrapper. When I did initial prototyping of image support on the Mac version of Permanent Marker, this worked fine — I’d create a FileWrapper for my image file, pass the FileWrapper to the NSTextAttachment, and boom, there was an image.

My surprise happened when I started resizing the image before trying to show it. I no longer used NSTextAttachment(fileWrapper:) and instead used NSTextAttachment(data:ofType:). Suddenly my images stopped showing up.

And that’s when I learned about a MacOS-specific NSTextAttachment property: attachmentCell. If you actually want to display an attachment on MacOS, you need to create an object that conforms to NSTextAttachmentCellProtocol and assign it to the attachmentCell property. The built-in NSTextAttachmentCell class conforms to the protocol, and you can easily create one for an image using the NSTextAttachmentCell(imageCell:) initializer. Apparently the NSTextAttachment(fileWrapper:) initializer takes care of setting up the attachmentCell for you, so you only need to learn about this trick when you need to manipulate data directly rather than using data straight from a file.

Working on Permanent Marker is giving me quite the education on the differences between UIKit and AppKit!