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.)
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.)
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)")
}
}
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()
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.
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];
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.
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()
}
}
The simple, accepted answer from 2011 has gotten more complex. Apple has deprecated the launch()
function as of as of 10.14, suggesting that we "use run()
instead". Unfortunately, it's not a direct replacement. Fortunately, most of the original answer still holds, with a simple change.
Instead of the original line:
task.launch()
when you use run()
you have to use it with try
to catch possible errors. The line becomes:
try task.run()
The accepted answer above then becomes:
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]
try task.run()
However, this only works if the code is at the top level, not if it is inside a function. run()
throws errors while launch()
does not. If you use try
inside a function, the function also has to be declared to throw
errors. Or you can handle possible errors from run()
inside the function.
This page from "Hacking With Swift" has examples of how to catch STDOUT
and STDERR
from the process.
I hope this helps. I'm very new at Swift and this is what worked for me.