SwiftAppleScriptBridge — using AppleScript in Swift, without the ceremony

Hi!

I’ve just published a small open-source package I’ve been using in my own macOS apps, and I thought this crowd might find it useful. I coded this myself a while back, and recently used Claude AI to make it into a public package with documentation and examples.

The short version: it lets a Swift app run AppleScript, pass values into it, and get typed values back. I kept needing this for apps that drive other apps — Adobe InDesign, mostly — and I couldn’t find anything that worked the way I wanted, so I wrote my own and have been using it for a while now.

AppleScriptObjC is good and I’m not trying to replace it. I just wanted direct interaction between the two languages with no middleman, and easy conversion of the common types — strings, integers, reals, booleans, lists and records — in both directions.

You write your script with $placeholders and fill them from Swift:

let countOpenWindows = AppleScriptBridge.AppleScriptObject(
    name: "countOpenWindows",
    returnType: .int,
    script: """
        tell application id "com.apple.finder"
            return count of windows
        end tell
    """
)

let count = try AppleScriptBridge.executeAppleScript(countOpenWindows) as? Int ?? 0

Two things it handles that are tedious by hand: first, every string substituted into a script is escaped, so a file named My “best” shot.jpg doesn’t break your script — or worse, inject into it. Second, you declare what the script returns and get a parsed Swift value back rather than picking apart a descriptor yourself.

Where it’s actually been worth it for me is speed. If you keep the logic in Swift and use AppleScript only to tell the other app to do the things that only AppleScript can do — no list building, no calculations, one task per script — it’s dramatically faster than the equivalent all-AppleScript solution. You also get documentation on hover and autocompletion for your scripts in Xcode, which is absent in Script Editor.

One thing worth knowing up front: an app using this can’t be sandboxed, so it can’t go on the Mac App Store. That’s Apple events in general, not something specific to my package. Developer ID signing and notarization work normally, so direct distribution is fine.

macOS 13+, no dependencies, MIT licensed:

It’s version 1.0.0, so I’d genuinely appreciate people kicking the tyres. If you hit something odd, or there’s an obvious thing I’ve missed from the perspective of someone who lives in AppleScript rather than Swift, tell me — issues and PRs welcome, and so are replies here.

Tx!
Fred

1 Like

Very interesting, Fred,

I was facing very similar challenges in executing AppleScripts from within my app. The issue for me was that I wanted my app’s users to be able to execute their scripts, the same ones they run every day, without having to convert them to apps or rewrite them.

I built this as a separate module with help on GitHub and it’s open source.

1 Like

This is indeed interesting, especially the concept of AppleScriptReturnType!

On the one hand, most of the time when writing a script you have a specific return type in mind, and if the result isn’t that type it should be an error. That frame of mind translates well to swift and is captured really well here.

On the other hand, I know of cases where I would want to handle the result with “if case let” type of logic to switch on the type of the result. A concrete example I have in mind is the case of a property usually being a floating point value, but sometimes being infinity, which would be represented as an AppleScript enum, as opposed to a Double. So, I’d want to be able to execute the AppleScript, but have the result be available before automatic coercion to Double. Of course, that’s the kind of “ceremony” this Bridge is meant to avoid, so adding that kind of thing would violate the spirit of the project.

In any case, thank you for sharing your work! Its on my github watch list.

1 Like

Glad you like it! I’ve opened an issue, let me know if it is what you have in mind and I’ll work on it. :slight_smile: