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];

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

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’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!!!