My app (swift, OSX, Xcode, latest versions of everything) has an NSTextView that I allow the user to type in to. The NSTextView has RichText, graphics, and and NSInspectorBar enabled.
I load/save the contents of the NSTextView to webarchive format (grumble grumble - but I need a portable format that includes images).
So far so good. Everything works... except for a minor detail. When I insert an image into the TextView, it doesn't show up. But it is there, because if I save and load... it appears.
Here's how I insert the image into the NSTextView (I get the path to the image from an NSOpenPanel:
guard let theImage = NSImage(contentsOfURL: openPanel.URL!) else {
showErrorMessage("Unable to load image")
return
}
// self.textEditor is the NSTextView
guard let store = self.textEditor.textStorage else { abort() }
let attachment = NSTextAttachment()
attachment.image = theImage
let attrString = NSAttributedString(attachment: attachment)
let range = self.textEditor.selectedRange()
store.replaceCharactersInRange(range, withAttributedString: attrString)
I think its reasonably straight forward: load the image, add to an attributedString, replace the selected characters with the attributedString.
However nothing appears. No change at all to the NSTextView. If I save the NSTextStorage contents as a webarchive:
guard let store = textEditor.textStorage else { abort() }
let attr: [String: AnyObject] = [NSDocumentTypeDocumentAttribute: NSWebArchiveTextDocumentType]
let data = try store.dataFromRange(NSMakeRange(0, store.length), documentAttributes: attire)
//... save 'data' to DB ...
and then reload them back in:
//... load 'data' from the DB ...
guard let store = textEditor.textStorage else { abort() }
let str = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSWebArchiveTextDocumentType], documentAttributes: nil)
store.setAttributedString(str)
...the images appear in exactly the right spot.
I'm quite confused by this one. Does anyone have any ideas as to what I am missing?
BTW - I have tried forcing a refresh using:
let frame = self.textEditor.frame
self.textEditor.setNeedsDisplayInRect(frame)
That did not work.
ADVthanksANCE for any advice you can give. Cheers. Paul
Edit: Addendum... I've been messing with NSTextAttachmentCells in the hope that they might work.They fail in a completely different fashion.
If I just replace the image insert code above with:
let cell = NSTextAttachmentCell(imageCell: theImage)
let txtAtt = NSTextAttachment()
txtAtt.attachmentCell = cell
let str = NSAttributedString(attachment: txtAtt)
let range = self.textEditor.selectedRange()
store.replaceCharactersInRange(range, withAttributedString: str)
...then the images do appear immediately upon insertion, and in exactly the right place. But they do not get saved! That is, when I do a save and reload (as per the code above), they do not get included in the webarchive.
Edit^2: So the obvious conclusion from the above is to have both an image attached to the TextAttachment and an image attached to an NSTextAttachmentCell, which is also attached to the TextAttachment. Well... that doesn't work. BUT what does work is this:
let cell = NSTextAttachmentCell(imageCell: theImage)
let txtAtt = NSTextAttachment(data: theImage.TIFFRepresentation, ofType: kUTTypeTIFF as String)
txtAtt.attachmentCell = cell
//txtAtt.image = theImage
let str = NSAttributedString(attachment: txtAtt)
let rng = self.textEditor.selectedRange()
store.replaceCharactersInRange(rng, withAttributedString: str)
That is... I create a TextAttachment, passing data of the TIFF representation of the image. I also create the AttachmentCell. This now works - the insertion is visible and it saves.
Only downside is that TIFF's are huge - the image expands to 3x the memory size.
Can anyone give some ideas as to why this works?
Ok - here's the solution.
The NSTextAttachmentCell represents
...the interface for objects that draw text attachment icons and handle mouse events on their icons apple docs.
So you need to create one of these using an image.
The NSTextAttachment
...contains either an NSData object or an NSFileWrapper object, which in turn holds the contents of the attached file apple docs
So you need both - one for display and interaction, one for saving (i.e. is the actual data). The issue I had with saving TIFF (too large) can be overcome by creating JPG or PNG representations, which can quite happily be used for the NSData object. I did that by extending NSImage:
extension NSImage {
var imagePNGRepresentation: NSData {
return NSBitmapImageRep(data: TIFFRepresentation!)!.representationUsingType(.NSPNGFileType, properties: [:])!
}
var imageJPGRepresentation: NSData {
return NSBitmapImageRep(data: TIFFRepresentation!)!.representationUsingType(.NSJPEGFileType, properties: [:])!
}
}
(Thanks to commenters from here on how to do this).
So here is the code that can actually insert an image into an NSTextView (drum roll please)...
guard let theImage = NSImage(contentsOfURL: openPanel.URL!) else {
showErrorMessage("Unable to load image")
return
}
guard let store = self.textEditor.textStorage else { abort() }
let cell = NSTextAttachmentCell(imageCell: theImage)
let txtAtt = NSTextAttachment(data: theImage.imageJPGRepresentation, ofType: kUTTypeJPEG as String)
txtAtt.attachmentCell = cell
let str = NSAttributedString(attachment: txtAtt)
let range = self.textEditor.selectedRange()
store.replaceCharactersInRange(range, withAttributedString: str)
When I save this as a webarchive, I can see the JPG objects being saved along side the HTML.
Extending this... you can probably insert any data type (e.g. docs) and use an icon representation in the Cell. Cool.
Weird thing is that most web pages where this is described do not include the Cell. I honestly don't know how that works.
Cheers. Paul.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With