13

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);
}
joels
  • 7,249
  • 11
  • 53
  • 94
  • Can you add code showing exactly what you are doing, how you are attempting to access the image data, etc. Without such information folk trying to help you will just be guessing. – CRD Apr 20 '14 at 23:56

3 Answers3

12
  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.

user3555093
  • 316
  • 2
  • 2
  • Now my file wrapper is being set. I accepted this answer since it works if you are ok with writing to disk. – joels Apr 21 '14 at 05:33
3

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)

Emerald Weapon
  • 2,392
  • 18
  • 29
3

@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
}
Grimxn
  • 22,115
  • 10
  • 72
  • 85