Flo Ragossnig
Flo Ragossnig

Reputation: 1470

How to run AppleScript from inside a SwiftUI View?

I'm kind of getting some more understanding with basic SwiftUI but now I wanted to extend my application to actually do some stuff regarding system components for me. Basically I want to run an AppleScript from inside my app which creates a signature in Mac Mail. The script itself is pretty simple:

// Generates a signature in Mac Mail
tell application "Mail"
    set newSig to make new signature with properties {name:"The Signature Name"}
    set content of newSig to "My New Signature Content"
end tell

I have created a view with a button which should execute the script:

import SwiftUI

struct SomeView: View {

    @State var status = ""

    var body: some View {

        VStack(alignment: .center) {
            Button(action: {

                let source = """
                tell application \"Mail\"
                    set newSig to make new signature with properties {name: \"The Signature Name\"}
                    set content of newSig to \"My New Signature Content\"
                end tell
                """

                var error: NSDictionary?
                if let scriptObject = NSAppleScript(source: source) {
                    if let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error) {
                        self.status = output.stringValue ?? "some default"
                    } else if (error != nil) {
                        self.status = "error: \(error)"
                    }
                }

            }) {
                Text("Generate").font(.callout)
            }
            Text("\(self.status)")

        }
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Everything executes but I get the error

AppleScript run error = {
    NSAppleScriptErrorAppName = Mail;
    NSAppleScriptErrorBriefMessage = "Application isn\U2019t running.";
    NSAppleScriptErrorMessage = "Mail got an error: Application isn\U2019t running.";
    NSAppleScriptErrorNumber = "-600";
    NSAppleScriptErrorRange = "NSRange: {0, 0}";
}

After some research i found this article which describes the problem quite well. Apparently this error is because the app is running in a sandbox and within the sandbox Mail is indeed not running. I kind of get Apple's idea not to let applications do whatever they want without the user's consent...

Anyway, unfortunately this article describes the solution using Objective C and this is something I have even less of a clue than SwiftUI.

Can anybody tell me how to run (or copy my script.scpt file to the accessible library folder and the run) a script from within a SwitUI View? This would be so much help!

Thanks!!!

Upvotes: 5

Views: 1950

Answers (1)

Flo Ragossnig
Flo Ragossnig

Reputation: 1470

I have played around quite a bit with this manner and after having numerous discussions and trial & errors on that subject, I want to present 2 solutions (until now it seems that these are the only possible solutions for a sandboxed application).

First of all, if you don't consider to distribute your app on the App Store, you can forget about the following, since you just have to deactivate the Sandbox and you are basically free to do whatever you want!

In case your planning to distribute a Sandboxed App, the only way of running a script and interacting with other apps on the user's system is to run a script file from the Application Script folder. This folder is a designated folder in the user library structure: /Users/thisUser/Library/Application Scripts/com.developerName.appName/. Whatever script goes in here you have the right to run from your application appName.

You basically have two options to get your script file into that folder:

Option 1 - Install the Script File

This is (in my opinion) clearly the option you should go for if your script is static (does not require any additional user data from your application). All you have to do is

  • Select the project (1)
  • click on Build Phases (2)
  • add the Copy Files setting (if not already present)
  • choose the script file location in your app (singing might be a good option when distributing via AppStore)
  • add the Application Script folder of your application to the destination. Therefore, choose Absolute Path and enter Users/$USER/Library/Application Scripts/$PRODUCT_BUNDLE_IDENTIFIER in the Path field.

You can also select the Copy only when installing option if your script is entirely static but in case you have changes on the script when the application is closed and reopened and you want to update the script, leave this option blank (I have not tested this!).

enter image description here

After this is done you can execute the script via NSUserScriptTask. A more detailed description of how you could implement this is given here.

Option 2 - Giving access to the folder and copy the file on demand

This is certainly the solution when your script file updates dynamically according to e.g. user inputs. Unfortunately, this is a bit of a hassle and does not have (in my opinion) satisfying solutions. To do so, you will have to grant access to the folder (in this case Application Scripts/). This is done via NSOpenPanel. A really good tutorial how to implement this is given here.

Per default you will have read permission to that folder only. Since you are trying to copy a file into that folder you will have to change that to read/write in your Capabilities as well.

enter image description here

I hope this will help some people to "shine some light into the dark"! For me this was quite a bit of a journey since there is only very little information out there.

Upvotes: 6

Related Questions