Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-03-28

Swift and NSPasteboard

Some things never change, and some things do. One of the things that changed is the usage of NSPasteboard in Swift.
I am still recoding some stuff from Objective-C to Swift, and the thing that kept me awake is how to handle copy & paste in swift.

First things first, the Objective-C code looked as follows:

-(void)paste:(id)sender {
        
    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
    NSArray *classArray = [NSArray arrayWithObject:[NSString class]];
    NSDictionary *options = [NSDictionary dictionary];
    
    if ([pasteboard canReadObjectForClasses:classArray options:options])
    {
        NSArray *strings = [pasteboard readObjectsForClasses:classArray options:options];
            
        [datasource cmdPaste:strings];
    }
}

This is pretty much exactly as Apple suggests it should be, first get the general pasteboard, then check if the data is available in the requested format, then retrieve it from the pasteboard and finally using it.

The first approach to convert this to Swift was straightforward:

    func paste(sender: AnyObject?) {
                        
        let pasteboard = NSPasteboard.generalPasteboard()
        
        if pasteboard.canReadObjectForClasses([NSPasteboardTypeString], options:nil) {

            if let strings = pasteboard.readObjectsForClasses([NSPasteboardTypeString], options:nil) as? Array<String> {                
                
                dataSource.cmdPaste(strings)
            }
        }
    }

As you can see, the code is pretty much 1:1 converted into Swift. Not very "swifty" though, but that is to be expected when converting code quick & dirty.

However, while this code compiles fine, it does not work. At runtime it will throw exceptions about missing implementations for selectors:

-[__NSCFConstantString readableTypesForPasteboard:]: unrecognized selector sent to instance 0x7fff7729d5c0

I was unable to find something about this on the internet, seems I am the only one experiencing this problem!?! I tried several variations, but in the end I had to give up, nothing seemed to work. Sooner or later I would get the same message.

Next I tried to reuse the old Objective-C code, i.e. I introduced an Objective-C class that would read the strings from the pasteboard. Interestingly that did not work either !?! The same error would occur. But when I compiled and ran the old project (with the same version of Xcode) it would run without problems. Perhaps something in the runtime kernel? I don't know.

Since the Objective-C code was quite old, I tried another look at the documentation. But doing that with the old mind set did not yield any solution either. Until I hit upon the proper search string in google. There I found a blog entry that showed how to read a string from the pasteboard. And using that information I was also able to create a much more swifty way of handling the pasteboard:

    func paste(sender: AnyObject?) {
                
        let pasteboard = NSPasteboard.generalPasteboard()
        
        if let nofElements = pasteboard.pasteboardItems?.count {
            
            if nofElements > 0 {
                
                
                // Assume they are strings
                
                var strArr: Array<String> = []
                for element in pasteboard.pasteboardItems! {
                    if let str = element.stringForType("public.utf8-plain-text") {
                        strArr.append(str)
                    }
                }
                
                
                // Exit if no string was read
                
                if strArr.count == 0 { return }
                
                
                // Perform the paste operation
                
                dataSource.cmdPaste(strArr)
           }
        }        
    }

This approach is also much more in-line with the current guidelines from Apple on how to use the pasteboard. It clearly uses the pasteboard as a storage for (possibly multiple) items that each can have a different type (UTI). As usual, I am now quite pleased with how this looks in Swift.

Happy coding

2015-06-17 Added more pboard and drag &drop stuff in this post

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2 comments:

  1. You've got an error in your Swift code. You're passing an array of Strings instead of array of Classes. That's why there's unrecognized selector error.
    Instead of readObjectsForClasses([NSPasteboardTypeString], options:nil) it should be readObjectsForClasses([NSString.type], options:nil)

    ReplyDelete
  2. Thanks .max
    I have not checked your answer, but it makes sense. Still, I now prefer the solution in the lower half of the post, which is also the way that Apple now seems to prefer.

    ReplyDelete