Pass Argument to AppleScript from Objective C

I’ve embedded an AppleScript into my Xcode project. The script requires an argument. How can I pass an argument when executing the script in Objective-C?

NSString *path = [[NSBundle mainBundle] pathForResource:@“myEmbeddedScript” ofType:@“scpt”];
NSURL *url = [NSURL fileURLWithPath:path];NSDictionary *errors = [NSDictionary dictionary];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithContentsOfURL:url error:&errors];
[appleScript executeAndReturnError:nil];

I have done that… if I remeber correctly I did it with NSTask not NSAppleScript.
I believe you could find solution here in this forum if you do a search.

Use OSAScript from the OSAKit framework. Load it using initWithContentsOfURL:error: and then use executeHandlerWithName:arguments:error:.

If you do not get it to work you could always use NSTask and use osascript and arguments

Here is a example in AS do demostrate it but instead of osascript I use run script.

set ASScript to "
on run argv
display dialog \"Hello \" & (item 1 of argv)
end
"

run script ASScript with parameters "World!"

It works great with the OSAKit.
Take your AS and drop into project, copy it checking the target.
Thanks All!

#import <OSAKit/OSAKit.h>

NSString *path = [[NSBundle mainBundle] pathForResource:@"Test" ofType:@"scpt"];
NSURL *url = [NSURL fileURLWithPath:path];NSDictionary *errors = [NSDictionary dictionary];
NSArray *myArg =  [NSArray arrayWithObject:@"Hello World"];
NSString *myMethod = @"DisplayTest";
OSAScript *appleScript = [[OSAScript alloc] initWithContentsOfURL:url error:&errors];
[appleScript executeHandlerWithName:myMethod arguments:myArg error:&errors];

I did a fast example in AppleScriptObjC with NSTask.

This is the script that use osascript, run and choose the second script below.

use framework "Foundation"
use scripting additions

set theScript to POSIX path of (choose file)

set argumentList to {theScript, "again world!"}

set executableURL to current application's |NSURL|'s fileURLWithPath:"/usr/bin/osascript"
set {theTask, theError} to current application's NSTask's launchedTaskWithExecutableURL:executableURL arguments:argumentList |error|:(reference) terminationHandler:(missing value)
theTask's waitUntilExit()

set status to theTask's terminationStatus()
if status as integer is 0 then
	log "Task succeeded."
else
	log "Task failed."
end if

This is the script that will be executed and take 1 arguments, save it as scpt

on run argv
	display dialog "Hello " & (item 1 of argv)
end run

AppleScriptObjC version of AS run script command… :slight_smile:

use framework "Foundation"
use scripting additions

(**
	Sample script to run with 1 arguments
	
	on run argv
		display dialog "Hello " & (item 1 of argv)
	end run
**)

set theScript to POSIX path of (choose file)

its runScript:theScript withParameters:{"again World!"}

on runScript:theScript withParameters:arguments
	set argumentList to {theScript} & arguments
	set executableURL to current application's |NSURL|'s fileURLWithPath:"/usr/bin/osascript"
	set {theTask, theError} to current application's NSTask's launchedTaskWithExecutableURL:executableURL arguments:argumentList |error|:(reference) terminationHandler:(missing value)
	theTask's waitUntilExit()
	
	set status to theTask's terminationStatus()
	if status as integer is 0 then
		log "Task succeeded."
	else
		log "Task failed."
	end if
end runScript:withParameters:

I’m main programming exclusively in Objective-C, working a lot with iTunes.
I eventually moved to Utilizing Scripting Bridge with iTunes… but often
it’s very quirky and frustrating. And sometimes was just damn easier using
AppleScript. So I created what I need in a AppleScript I called ITunesHelper with
the functions I need. Here is a quicker way I found to incorporate it by
Create your own “bridging header" file in XCode

Make sure your script is structured with

  • script Name
  • property parent : class “NSObject”

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use script "BridgePlus"

script ITunesHelper
	property parent : class "NSObject"
	(*
		-- classes, constants, and enums used
		-- functions
		--- all trimed
	*)
end script

Here’s my example AppleScript with the guts of the functions trimmed:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use script "BridgePlus"

script ITunesHelper
	property parent : class "NSObject"
	
	-- classes, constants, and enums used
	property NSMutableArray : a reference to current application's NSMutableArray
	property NSArray : a reference to current application's NSArray
	property NSMutableDictionary : a reference to current application's NSMutableDictionary
	property NSDictionary : a reference to current application's NSDictionary
	property NSString : a reference to current application's NSString
	property |NSURL| : a reference to current application's |NSURL|
	property relocatePlaylistName : "2022 Master Fix Relocate Playlist"
	
	on createPlaylistName:aName withTrackItems:trackItems
		-- TRIMMED PROCESSING
		return aPlaylistWithTracks
	end createPlaylistName:withTrackItems:
	
	on loadPlaylist:aPlaylist withTrackItems:trackItems
		-- TRIMMED PROCESSING
		return aPlaylist
	end loadPlaylist:withTrackItems:
	
	on findOrCreatePlaylistWithName:aName
		-- TRIMMED PROCESSING		
		return aPlaylist
	end findOrCreatePlaylistWithName:
	
	on findUserPlaylistsContainingName:aName
		-- TRIMMED PROCESSING		
		return foundPlaylists1
	end findUserPlaylistsContainingName:
	
	on findFolderPlaylistsContainingName:aName
		-- TRIMMED PROCESSING		
		return foundPlaylists1
	end findFolderPlaylistsContainingName:
	
	on findPlaylistWithID:aID
		-- TRIMMED PROCESSING
		return aPlaylist
	end findPlaylistWithID:
	
	on findOrCreateFolderWithName:aFolderName
		-- TRIMMED PROCESSING		
		return aFolder
	end findOrCreateFolderWithName:
	
	on movePlaylistNamed:aPlaylistName toFolderWithName:aFolderName
		-- TRIMMED PROCESSING		
		return moveOK
	end movePlaylistNamed:toFolderWithName:
	
	on moveFolderNamed:aFolderName toParentFolderWithName:aParentName
		-- TRIMMED PROCESSING
		return moveOK
	end moveFolderNamed:toParentFolderWithName:
	
	on movePlaylist:aPlaylist toFolderWithName:aFolderName
		-- TRIMMED PROCESSING
		return moveOK
	end movePlaylist:toFolderWithName:
	
	on moveITunesItem:aItem toFolder:aFolder
		-- TRIMMED PROCESSING		
		return moveOK
	end moveITunesItem:toFolder:
	
	on shouldMoveITunesItem:aItem toParent:aFolder
		-- TRIMMED PROCESSING		
		return moveIt
	end shouldMoveITunesItem:toParent:
	
	-- UTILITIES
	on addToRelocatePlaylist:aTrack
		return (my addToPlaylistName:relocatePlaylistName ifNotContainsTrack:aTrack)
	end addToRelocatePlaylist:
	
	on addToPlaylistName:aPlaylistName ifNotContainsTrack:aTrack
		-- TRIMMED PROCESSING
		return aResult
	end addToPlaylistName:ifNotContainsTrack:
	
	on createAliasFromURL:aFileURL
		load framework
		set aPath1B to current application's SMSForder's HFSPathFromURL:aFileURL colonForPackages:false
		set aPath1C to aPath1B as text
		set aAlias to (aPath1C as alias)
		return aAlias
	end createAliasFromURL:
	
end script

Create an new file->New Header file
Name It the exact same name as your script.
This not the name of the file but the script name ie “ITunesHelper.h”

In the interface part layout all of your methods/functions as
They are in AppleScript.

My Objective-C Header File:


#import <Foundation/Foundation.h>
#import <AppleScriptObjC/AppleScriptObjC.h>
#import <ScriptingBridge/ScriptingBridge.h>
#import "iTunes.h"

@interface ITunesHelper : NSObject

-(id)createPlaylistWithName:(NSString*)aName
			 withTrackItems:(NSArray*)trackItems;

-(id)loadPlaylist:(id)aPlaylist
   withTrackItems:(NSArray*)trackItems;

-(id)addToPlaylistName:(NSString*)aName
	ifNotContainsTrack:(id)aTrack;
-(id)addToRelocatePlaylist:(iTunesFileTrack*)aTrack;
-(iTunesPlaylist*)findOrCreatePlaylistWithName:(NSString*)aName;
-(id)findOrCreateFolderWithName:(NSString*)aFolderName;


-(id)findUserPlaylistsContainingName:(NSString*)aName;
-(id)findFolderPlaylistsContainingName:(NSString*)aName;
-(id)findPlaylistWithID:(NSString*)aID;

-(BOOL)movePlaylistNamed:(NSString*)aPlaylistName
		toFolderWithName:(NSString*)aFolderName;

-(BOOL)movePlaylist:(id)aPlaylist
   toFolderWithName:(NSString*)aFolderName;

-(BOOL)moveFolderNamed:(NSString*)aFolderName
toParentFolderWithName:(NSString*)aParentName;

-(BOOL)moveITunesItem:(id)aItem
			 toFolder:(id)aFolder;

-(BOOL)shouldMoveITunesItem:(id)aItem
				   toParent:(id)aFolder;

-(id)createAliasFromURL:(NSURL*)aFileURL;

@end

You don’t have to have a .m file or define implementation
in you main.m add import Framework and Load Functions as below


#import <AppleScriptObjC/AppleScriptObjC.h>

int main(int argc, const char * argv[]) {
    [[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
    return NSApplicationMain(argc, argv);
}

In your Target Build Phases

  • add a Copy Files Phase (if doesn’t exist)
  • set the Destination to Resources
  • set the Subpath to Scripts
  • click plus and select your AppleScript file

Make Sure Your AppleScript file has the Application Selected as a Target
and double check in the in Build Phases that the AppleScript is in the Compile Sources

Make sure the Link Binary with Libraries contains:
AppleScriptObjC.framework

In the other classes where you want to use it.
You may want to put the import in the .m or
declare it as a class: Also include the following:

Here’s example in the .h file of another class.

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <AppleScriptObjC/AppleScriptObjC.h>
#import <ScriptingBridge/ScriptingBridge.h>
#import "ITunesHelper.h"

@class ITunesHelper;

in the .m file I declare private interface of the class and add a property for the class


@interface KTelITunesAppScriptBC ()

@property (nonatomic) iTunesApplication* iTunesApp;
@property (nonatomic) ITunesHelper* iTuneHelper;

@end

The tricky part is init your AppleScript as a class. Because as run time the compiler knows nothing
about your class. You can’t create it like this:


self.iTuneHelper = [[ITunesHelper alloc] init];

it will compile fine because we’ve declared the class ITunesHelper.
But a run time it will fail saying “No Known Class”.
So must create it in the following way:


        Class helpClass = NSClassFromString(@"ITunesHelper");
	self.iTuneHelper = [[helpClass alloc] init];

Now I can call my AppleScript From my Objective C code like in the following examples:


iTunesPlaylist* aPlaylist = [self.iTuneHelper findPlaylistWithID:aID];
iTunesPlaylist* aPlaylist1 = [self.iTuneHelper findOrCreatePlaylistWithName:aName];

-(id)findOrCreateFolderWithName:(NSString*)aFolderName  {
	return [self.iTuneHelper findOrCreateFolderWithName:aFolderName];
}

-(BOOL)movePlaylistNamed:(NSString*)aPlaylistName
		toFolderWithName:(NSString*)aFolderName {
	return [self.iTuneHelper movePlaylistNamed:aPlaylistName
							  toFolderWithName:aFolderName];
}

-(BOOL)moveITunesItem:(id)aItem
			 toFolder:(id)aFolder  {
	return [self.iTuneHelper moveITunesItem:aItem
								   toFolder:aFolder];
}

-(id)createAliasFromURL:(NSURL*)aFileURL  {
	return [self.iTuneHelper createAliasFromURL];
}


OH FEW OTHER IMPORTANT NOTES OF THINGS I FOUND:

  • sometimes in your custom bridging header file you may want to just return a id object
  • I also found that with my AppleScript code I found that I needed to convert my NSString* parameters that I was send to text before I could use them in my further processing:

Example below script would error when trying to get iTunes to use “aName” (NSString).
So I had to create aPlaylistName from aName as text and use it like that.


	on findOrCreatePlaylistWithName:aName
                set aPlaylistName to aName as text
		set aPlaylist to missing value
		set aNewPlaylist to missing value

		tell application id "com.apple.iTunes"	
			set playlistExists to exists (a reference to playlist aPlaylistName)
			if (playlistExists) then
				try
					set aNewPlaylist to playlist aPlaylistName
				on error theErr number theNum
					log {"Can't find aPlaylist " & theErr & " " & theNum & " " & aName}
				end try
			else
				set aNewPlaylist to (make new playlist with properties {name:aPlaylistName})
			end if
			set aPlaylist to get aNewPlaylist
		end tell
		return aPlaylist
	end findOrCreatePlaylistWithName:

For the most part it’s been pretty awesome and handy way to incorporate AppleScript into Obj-C.
But as most people report Scripting Bridge can be pretty quirky!!!