Search code examples
macoscocoanspasteboard

Storing and retrieving a custom object from NSPasteBoard


I have an object that belongs to this class that contains the following declarations:

HEADER

@interface MyClassObject : NSObject <NSCopying, NSPasteboardWriting, NSPasteboardReading>

@property (nonatomic, strong) NSArray *children;
@property (nonatomic, assign) NSInteger type;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) id node;

children holds other objects of this class, node holds objects that can be NSView or NSImageView.

I would like to be able to copy and paste objects of this type to/from NSPasteboard.

I have google around but the explanations are vague.

What should I do to make this class copiable/readable to NSPasteboard or in other words, make it conform to NSPasteboardWriting and NSPasteboardReading protocols?

I don't have the slightest clue on how this is done and as usual, Apple documetation and nothing is the same thing.


Solution

  • This is the complete solution.

    First thing is to make the class conforming to NSCoding too. In that case the class declaration will be

    @interface MyClassObject : NSObject <NSCopying, NSPasteboardWriting, NSPasteboardReading, NSCoding>
    

    To make it compatible with NSCoding you have to implement encodeWithCoder: and initWithCoder:... in my case:

    - (void)encodeWithCoder:(NSCoder *)coder {
    
      [coder encodeObject:@(self.type) forKey:@"type"];
      [coder encodeObject:self.name forKey:@"name"];
      // converting the node to NSData... 
      // the object contained in node must be compatible with NSCoding
      NSData *nodeData = [NSKeyedArchiver archivedDataWithRootObject:self.node];
      [coder encodeObject:nodeData forKey:@"node"];
    
      NSData *childrenData = [NSKeyedArchiver archivedDataWithRootObject:self.children];
      [coder encodeObject:childrenData forKey:@"children"];
    
    }
    
    - (id)initWithCoder:(NSCoder *)coder {
    
      self = [super init];
    
      if (self) {
    
        _type = [[coder decodeObjectForKey:@"type"] integerValue];
        _name = [coder decodeObjectForKey:@"name"];
    
        NSData *nodeData = [coder decodeObjectForKey:@"node"];
        _efeito = [NSKeyedUnarchiver unarchiveObjectWithData:nodeData];
    
        NSData *childrenData = [coder decodeObjectForKey:@"children"];
        _children = [NSKeyedUnarchiver unarchiveObjectWithData:childrenData];
        _parent = nil;  // I want this to be nil when the object is recreated
      }
    

    To make the class work with NSPasteboard you have to add these 4 methods:

    -(id)initWithPasteboardPropertyList:(id)propertyList ofType:(NSString *)type {
      return [NSKeyedUnarchiver unarchiveObjectWithData:propertyList];
    }
    
    
    +(NSArray *)readableTypesForPasteboard:(NSPasteboard *)pasteboard {
        // I am using the bundleID as a type  
        return @[[[NSBundle mainBundle] bundleIdentifier]];
    }
    
    - (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard {
      // I am using the bundleID as a type  
      return @[[[NSBundle mainBundle] bundleIdentifier]];
    }
    
    
    - (id)pasteboardPropertyListForType:(NSString *)type {
      // I am using the bundleID as a type  
      if(![type isEqualToString:[[NSBundle mainBundle] bundleIdentifier]]) {
        return nil;
      }
    
      return [NSKeyedArchiver archivedDataWithRootObject:self];
    }
    

    Then, on the class you implement the copy and paste you add:

    - (void)copy:(id)sender {
    
      NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
      [pasteBoard clearContents];
    
      NSArray *copiedObjects = @[theObjectYouWantToCopyToThePasteboard];
      [pasteBoard writeObjects:copiedObjects];
    
    }
    
    - (void)paste:sender {
    
      NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
      NSArray *classArray = @[[LayerObject class]];
      NSDictionary *options = [NSDictionary dictionary];
    
      BOOL ok = [pasteboard canReadItemWithDataConformingToTypes:@[[[NSBundle mainBundle] bundleIdentifier]]];
    
      if (ok) {
        NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
        MyObjectClass *object = [objectsToPaste objectAtIndex:0];
    
        // object is the one you have copied to the pasteboard
        // now you can do whatever you want with it.
        // add the code here.    
      }
    }