Thomas Kilian Thomas Kilian - 1 year ago 65
Swift Question

Establish a service in Finder context

I'm trying to add a service to the Finder's context menu using this class:

public class Service {
public func handleServices(pboard:NSPasteboard, userData:String, error:UnsafeMutableBufferPointer<String>) { // not sure about the correct parameters
if (pboard.types?.contains(NSFilenamesPboardType) != nil) {
let fileArray = pboard.propertyListForType(NSFilenamesPboardType)
print(fileArray)
}
}

init () {
NSApp.servicesProvider = self
NSUpdateDynamicServices()
}
}


The service is announced in info.plist as follows:

<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Service Handling Demo</string>
</dict>
<key>NSMessage</key>
<string>handleServices</string>
<key>NSPortName</key>
<string>services</string>
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
</array>
</dict>
</array>


Finally I have turned on the service in System Preferences/Keyboard/Shortcuts. So I see the service and can call it. But all I get when calling it is


Cannot find service provider for selector handleServices:userData:error: or handleServices:: for service handleServices

Answer Source

There are two problems in your code:

  • Objective-C messages are sent to the service provider, therefore the Swift method must be "Objective-C compatible". This can be achieved by subclassing NSObject, or by marking the method with the @objc attribute.

  • The service handler method has the signature

    - (void)handleServices:(NSPasteboard *)pboard
                  userData:(NSString *)userData
                     error:(NSString **)error
    

    which is mapped to Swift as

    func handleServices(pboard: NSPasteboard!,
                      userData: String!,
                         error: AutoreleasingUnsafeMutablePointer<NSString?>)
    

So this would be a correct version (which worked in my test):

public class Service {

    @objc public func handleServices(pboard: NSPasteboard!,
        userData: String!, error: AutoreleasingUnsafeMutablePointer<NSString?>) {

        // ...
    }

    init() {
        NSApp.servicesProvider = self
        NSUpdateDynamicServices()
    }
}

Some more remarks:

if (pboard.types?.contains(NSFilenamesPboardType) != nil) { ... }

is "optional chaining" and checks if the contains() method could be called on pboard.types, in other words it checks only if pboard.types != nil. What you probably want is to check if pboard.types != nil and the contains() method returns true. This can be achieved with the "nil-coalescing operator" ??:

if (pboard.types?.contains(NSFilenamesPboardType) ?? false) { ... }

Next,

pboard.propertyListForType(NSFilenamesPboardType)

is documented to return an optional array of NSStrings, so you could unwrap and convert that to a String array with

if let fileArray = pboard.propertyListForType(NSFilenamesPboardType) as? [String] { ... }

Finally, assigning an error string (to the pointer provided by the caller) would be done with

if (error != nil) {
    error.memory = "My error description"
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download