As many others, my main source of music (if not only) is Spotify. Recently Spotify removed the Musixmatch® lyrics feature from their desktop applications. Only after this did I realize that I only knew the catchy choruses of songs and would hum the rest of it. For a few days I googled for the lyrics, but that was a drag… So, I took a stroll on the Spotify webpage to check out their APIs. A few Web endpoints, iOS SDK, blah blah blah… Hold up, hold up, hold up! AppleScript API?! I’m no AppleScript expert, I’ve only used it a few times before. I really like the concept of AppleScript, but I hate the code. Apple, by trying to make the code more natural language like, made it amazingly annoying. But, at the same time, as I was reading though the documentation — it was right there! “Get the ID of the currently playing track” — A MacOS Application which get the lyrics of the currently playing track from Spotify! I wouldn’t have to hum songs anymore, I thought to myself!
Up until now, I’ve used AppleScript and Swift in disjoint. I had a gut feeling that this wouldn’t be easy. At first I thought I could use AppleScript to write the current track to a file and the MacOS application could read that file to fetch the lyrics. But then, that make me cringe. I started looking into AppleScript in Xcode projects. As I suspected — not a lot of documentation. A few Stack Overflow questions here and two tutorials in OBJECTIVE C there. YUCK! So, I got to work with what I had.
So, without further adieu… Let’s see the how to execute, pass, and return values from AppleScript in a Swift 3 project. (Swift for the win! YAY!)
We can find the Spotify AppleScript APIs here; a little snippet of AppleScript to fetch the current song is given as an example. After a little bit of modification we have this:
We are creating an class SpotifyScriptObj with parent class as NSObject on AppleScript. The above script fetches the current track playing on Spotify and displays a notification and also returning the track and artist name.
Our goal is to call the above functions from Swift.
In SpotifyScript.swift we use the NSClassFromString() method to obtain the SpotifyScriptObj class object we declared in the AppleScript file. We then allocate the object using alloc() which returns a new instance of the object. This new instance is of type AnyObject? which is returned back to the calling function.
Now we need to create a protocol for SpotifyScriptObj to conform to.
We declare the properties and methods that we want to access from AppleScript class SpotifyScriptObj. In this case we use demoProp and demoHandler() for testing purposes and getCurrentPlaying(), obviously to get the track currently playing.
As you might have noticed the odd @objc(NSObject) prefixed before the protocol. To explain this we need to understand how objects are named during runtime. Assume an object named Car in a project named Factory, the object during runtime will be named “Factory.Car”. @objc(…) renames the object’s runtime name to the one specified within the parenthesis, in this case, NSObject. The need to do this is because SpotifyScriptObj is not a Swift class and does not have a declaration in Swift for the compiler to understand. Since swift is a “safe” language, it does not allow unknown method calls on NSObject unlike Objective C. Swift throws an error on the lines of ‘Value of type … has no member …’ if unknown method calls are made on NSObject. By essentially renaming the AppleScriptProtocol protocol to NSObject we are telling the compiler that declared properties and methods are present in NSObject and it is safe to call them.
We’re almost done! Don’t forget to link the AppleScriptObjC framework to your target. Here SpotifyScript.getTrack() returns an AnyObject? which indeed is the SpotifyScriptObj! Cast the AnyObject? as AppleScriptProtocol and voilà! All done! You can now call the methods declared in your AppleScript, pass arguments, set properties, get return values… The whole lot! As an extra note we’ve displayed the type of AppleScriptProtocol as you can see in your console it type will be NSObject as expected!
Take a look at part 2 of AppleScript and Swift to understand about
NSAppleEventDescriptor and how to pass arguments from Swift to AppleScript.