ScriptingBridge - Communicating with Swift and AppleScript
AppleScript is a great technology on macOS for both developers and power users. With AppleScript, users can create automated processes which work other apps. As a developer though, sometimes you want a build an app in Xcode with the power of AppleScript without the need to have separate script files. That's where ScriptingBridge comes in. To work with AppleScript in any app, there are two options to do this:
- Write a separate AppleScript file and use the NSAppleScript API to execute and run the script.
- Use ScriptingBridge to work with AppleScript in Swift or Objective-C.
We are going to look at the second option: ScriptingBridge. In particular, how to use ScriptingBridge in Swift without the need for AppleScript files or event Objective-C bridging.
The Drawbacks of ScriptingBridge
If you intend to write an App for the Mac App Store there are restrictions as noted by Apple. You can also look at this article by Craig Hockenberry which shows how you can use the AppleScript API in a Mac Store app. The bottom line is: You can not use ScriptingBridge with an app in the Mac App Store.
Objective-C ScriptingBridge to Swift ScriptingBridge
When it comes to Objective-C ScriptingBridge, Apple already provides tools to prepare the code. Luckily, Tony Ingraldi of Majesty Software, has a great set of python scripts for creating the same code in Swift. For more details on this specifically, take a look his repo on GitHub or his blog post here.
ScriptingBridge with Safari
Now let’s try doing this by writing an app which pulls the tab urls out of a Safari window. The repo for this app can be found here. Building the Swift Code from AppleScript Definition Download the python scripting tools from the SwiftScripting repo on GitHub. From the repo’s directory, run the following commands:
pip install clangsdef /Applications/Safari.app > Safari.sdefsdp -fh --basename Safari Safari.sdef./sbhc.py Safari.h./sbsc.py Safari.sdef
Let’s break this down:
pip install clang
- ensures clang is installed for pythonsdef /Applications/Safari.app > Safari.sdef
- gets the scripting definition from the specified scriptable applicationsdp -fh --basename Safari Safari.sdef
- transforms a scripting definition to an Objective-C header file./sbhc.py Safari.h
- transforms the Objective-C header file to Swift./sbsc.py Safari.sdef
- extracts the enums from the standard definition to Swift
Now you should have 4 new files:
- Safari.sdef — the scripting definition file
- Safari.h — the objective-c header file, which we would use if we were doing objective-c directly or using objective-c bridging
- Safari.swift — the primary file containing the main ScriptingBridge API to the application
- SafariScripting.swift — the necessary enums needed by the ScriptingBridge API for the application
Since we are using only Swift code, we will only need the two Swift files (Safari.swift and SafariScripting.swift) in our application. Add the two files to your project in Xcode and now in our application, we can talk to Safari.
Using the ScriptingBridge API for Safari
To pull the all Safari windows currently open:
if let application = SBApplication(bundleIdentifier: "com.apple.Safari") { let safariApplication = application as SafariApplication let safariWindows = safariApplication.windows?().flatMap({ $0 as? SafariWindow }) ...}
We call the SBApplication
constructor using the bundle identifier. If an object is returned, we cast as the SafariApplication
protocol and get all the windows. The windows
property only returns a SBElementArray
, so we need to cast those elements to a SafariWindow
. Therefore by using the SafariWindow, we can get the window’s set of tabs:
let safariWindow = safariWindows?.firstlet safariTab = safariWindow?.tabs?().firstObject as? SafariTablet url = safariTab?.URL
So, let’s break this down:
let safariWindow = safariWindows?.first
- get the first Safari windowlet safariTab = safariWindow?.tabs?().firstObject as? SafariTab
- grab the first tab of the Safari window and cast it to SafariTablet url = safariTab?.URL
- get the url of that particular tab
If you need more details, check out the repo for the sample app called jURLnal which copies the URLs to the clipboard.
Conclusion
So as you can see -
- We have used the standard ScriptingBridge tools to build the standard definition and Objective-C header file of Safari’s AppleScript API.
- By using a set of python scripts from Tony Ingraldi of Majesty Software, we can convert the Objective-C header file and standard definition to Swift code.
- Adding the two Swift files to our project, we can use the API to extract the windows and tabs of the currently open Safari application.
If you want to learn more about Swift and ScriptingBridge, sign up at the form here.