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