Question

I have an NSTextView. I paste an image into it and see it. When I get the NSTextAttachment for the NSAttributedString of the text view, it's file wrapper is nil. How do I get the image data that was pasted into the text view?

I'm using a category on NSAttributedString to get the text attachments. I would prefer not to write to disk if it's possible.

- (NSArray *)allAttachments
{
    NSError *error = NULL;
    NSMutableArray *theAttachments = [NSMutableArray array];
    NSRange theStringRange = NSMakeRange(0, [self length]);
    if (theStringRange.length > 0)
    {
        NSUInteger N = 0;
        do
        {
            NSRange theEffectiveRange;
            NSDictionary *theAttributes = [self attributesAtIndex:N longestEffectiveRange:&theEffectiveRange inRange:theStringRange];
            NSTextAttachment *theAttachment = [theAttributes objectForKey:NSAttachmentAttributeName];
            if (theAttachment != NULL){
                NSLog(@"filewrapper: %@", theAttachment.fileWrapper);
                [theAttachments addObject:theAttachment];
            }
            N = theEffectiveRange.location + theEffectiveRange.length;
        }
        while (N < theStringRange.length);
    }
    return(theAttachments);
}
Was it helpful?

Solution

  1. Enumerate the attachments. [NSTextStorage enumerateAttribute:...]
  2. Get the attachment's filewrapper.
  3. Write to a URL.

    [textStorage enumerateAttribute:NSAttachmentAttributeName
                            inRange:NSMakeRange(0, textStorage.length)
                            options:0
                         usingBlock:^(id value, NSRange range, BOOL *stop)
     {
         NSTextAttachment* attachment = (NSTextAttachment*)value;
         NSFileWrapper* attachmentWrapper = attachment.fileWrapper;
         [attachmentWrapper writeToURL:outputURL options:NSFileWrapperWritingAtomic originalContentsURL:nil error:nil];
         (*stop) = YES; // stop so we only write the first attachment
     }];
    

This sample code will only write the first attachment to outputURL.

OTHER TIPS

You can get the contained NSImage from the attachment cell.

Minimalistic example:

// assuming we have a NSTextStorage* textStorage object ready to go, 
// and that we know it contains an attachment at some_index
// (in real code we would probably enumerate attachments).

NSRange range;
NSDictionary* textStorageAttrDict = [textStorage attributesAtIndex:some_index
                                             longestEffectiveRange:&range 
                                                           inRange:NSMakeRange(0,textStorage.length)];

NSTextAttachment* textAttachment = [textStorageAttributesDictionary objectForKey:@"NSAttachment"];
NSTextAttachmentCell* textAttachmentCell = textAttachment.attachmentCell;
NSImage* attachmentImage = textAttachmentCell.image;

EDITING: OS X only (AppKit version)

@EmeraldWeapon's answer is good for Objective-C, but falls down in Swift, as in Swift the attachmentCell is not an NSTextAttachmentCell, but rather an NSTextAttachmentCellProtocol? (which does not provide .image) - so you need to cast it to a concrete instance before accessing the .image:

func firstImage(textStorage: NSTextStorage) -> NSImage? {
    for idx in 0 ..< textStorage.string.count {
        if
            let attr = textStorage.attribute(NSAttributedString.Key.attachment, at: idx, effectiveRange: nil),
            let attachment = attr as? NSTextAttachment,
            let cell = attachment.attachmentCell as? NSTextAttachmentCell,
            let image = cell.image {

            return image
        }
    }
    return nil
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top