Thursday, December 1, 2022

#1 2022-11-14 01:53:42 pm

FlameCoder
Member
From:: USA
Registered: 2009-10-28
Posts: 91

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


Filed under: Pass Argument

Offline

 

#2 2022-11-14 03:00:50 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 1089

Re: Pass Argument to AppleScript from Objective C

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.


Node-RED makes it easy to automate IoT

Offline

 

#3 2022-11-14 04:18:35 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 6798

Re: Pass Argument to AppleScript from Objective C

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


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Online

 

#4 2022-11-15 12:53:02 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 1089

Re: Pass Argument to AppleScript from Objective C

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.

Applescript:

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


run script ASScript with parameters "World!"


Node-RED makes it easy to automate IoT

Offline

 

#5 2022-11-15 01:07:35 pm

FlameCoder
Member
From:: USA
Registered: 2009-10-28
Posts: 91

Re: Pass Argument to AppleScript from Objective C

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

Offline

 

#6 2022-11-15 02:16:14 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 1089

Re: Pass Argument to AppleScript from Objective C

I did a fast example in AppleScriptObjC with NSTask.


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

Applescript:

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

Applescript:

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

Last edited by Fredrik71 (2022-11-15 02:37:53 pm)


Node-RED makes it easy to automate IoT

Offline

 

#7 2022-11-15 02:57:45 pm

Fredrik71
Member
Registered: 2019-10-23
Posts: 1089

Re: Pass Argument to AppleScript from Objective C

AppleScriptObjC version of AS run script command... smile

Applescript:

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:


Node-RED makes it easy to automate IoT

Offline

 

#8 2022-11-18 12:30:45 am

technomorph
Member
Registered: 2017-12-14
Posts: 302

Re: Pass Argument to AppleScript from Objective C

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"

Applescript:


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:

Applescript:

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:

Applescript:


#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

Applescript:


#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.

Applescript:

#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

Applescript:


@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:

Applescript:


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:

Applescript:


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:

Applescript:


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:aFileURL];
}

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.

Applescript:


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

Last edited by technomorph (2022-11-18 02:00:00 am)

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)