28
votes

I have a simple AppleScript that sends an email. How can I call it from within a Swift application?

(I wasn't able to find the answer via Google.)

6

6 Answers

31
votes

Tested: one can do something like this (arbitrary script path added):

import Foundation
 
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]
 
task.launch()
69
votes

As Kamaros suggests, you can call NSApplescript directly without having to launch a separate process via NSTask (as CRGreen suggests.)

Swift Code

let myAppleScript = "..."
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
    if let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(
                                                                       &error) {
        print(output.stringValue)
    } else if (error != nil) {
        print("error: \(error)")
    }
}
4
votes

You can try NSAppleScript, from Apple's Technical Note TN2084 Using AppleScript Scripts in Cocoa Applications https://developer.apple.com/library/mac/technotes/tn2084/_index.html

NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:
            @"\
            set app_path to path to me\n\
            tell application \"System Events\"\n\
            if \"AddLoginItem\" is not in (name of every login item) then\n\
            make login item at end with properties {hidden:false, path:app_path}\n\
            end if\n\
            end tell"];

returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
4
votes

I struggled few hours, but nothing worked. Finally I managed to run AppleScript through shell:

let proc = Process()
proc.launchPath = "/usr/bin/env"
proc.arguments = ["/usr/bin/osascript", "scriptPath"]
proc.launch()

Dunno is this the best way to do it, but at least it works.

4
votes

For anyone who is getting the warning below for Swift 4, for the line while creating an NSAppleEventDescriptor from zekel's answer

Non-optional expression of type 'NSAppleEventDescriptor' used in a check for optionals

You can get rid of it with this edited short version:

let myAppleScript = "..."
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
    if let outputString = scriptObject.executeAndReturnError(&error).stringValue {
        print(outputString)
    } else if (error != nil) {
        print("error: ", error!)
    }
}

However, you may have also realized; with this method, system logs this message to console everytime you run the script:

AppleEvents: received mach msg which wasn't complex type as expected in getMemoryReference.

Apparently it is a declared bug by an Apple staff developer, and is said to be 'just' a harmless log spam and is scheduled to be removed on future OS updates, as you can see in this very long apple developer forum post and SO question below:

AppleEvents: received mach msg which wasn't complex type as expected in getMemoryReference

Thanks Apple, for those bazillions of junk console logs thrown around.

3
votes

As of March 2018, I think the strongest answer on this thread is still the accepted answer from 2011. The implementations that involved using NSAppleScript or OSAScript suffered the drawbacks having some minor, but highly unpleasant, memory leaks without really providing any additional benefits. Anyone struggling with getting that answer to execute properly (in Swift 4) may want to try this:

let manager = FileManager()
// Note that this assumes your .scpt file is located somewhere in the Documents directory
let script: URL? = try? manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
if let scriptPath = script?.appendingPathComponent("/path/to/scriptName").appendingPathExtension("scpt").path {
    let process = Process()
    if process.isRunning == false {
        let pipe = Pipe()
        process.launchPath = "/usr/bin/osascript"
        process.arguments = [scriptPath]
        process.standardError = pipe
        process.launch()
    }
}