I'm trying to write an application for swift control iTunes. But when initializing the application returns an object of type AnyObject
, but must iTunesApplication.
This object does not respond to methods and variables iTunes. Who knows how to make it work?
var iTunes = SBApplication.applicationWithBundleIdentifier("com.apple.iTunes")
The iTunes.h header also holds classes that I need to access but cannot. These classes cause a compilation error as if they are not in a declared a iTunes.h.
Why is this happening to me is not yet clear.
The whole list of classes that are declared a iTunes.h in via @class:
@class iTunesPrintSettings, iTunesApplication, iTunesItem, iTunesAirPlayDevice, iTunesArtwork, iTunesEncoder, iTunesEQPreset, iTunesPlaylist, iTunesAudioCDPlaylist, iTunesLibraryPlaylist, iTunesRadioTunerPlaylist, iTunesSource, iTunesTrack, iTunesAudioCDTrack, iTunesFileTrack, iTunesSharedTrack, iTunesURLTrack, iTunesUserPlaylist, iTunesFolderPlaylist, iTunesVisual, iTunesWindow, iTunesBrowserWindow, iTunesEQWindow, iTunesPlaylistWindow;
For example in Objective - c you would use something like this to get the current track
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
NSLog(@"Current song is %@", [[iTunes currentTrack] name]);
But I cannot get an equivalent in swift to work.
In my swift project , I had issues with using the types defined in the generated iTunes.h file (linking errors and such).
The answer from markhunte explains that you can obtain a reference to the application object. But beyond that, I was getting compilation/linker errors when trying to obtain instances from that application object.
In my swift project, I ended up creating an objective C wrapper class that exposes the iTunes types as basic objective C types (arrays and dictionary), and adapts methods as well.
My swift classes use this wrapper instead of the iTunes types.
So, the objective C wrapper looks like this (redux):
#import "ITunesBridgex.h"
#import "iTunes.h"
@interface ITunesBridgex(){
iTunesApplication *_iTunesApplication;
iTunesSource* _iTunesLibrary;
}
@end
@implementation ITunesBridgex
-(id)init {
self = [super init];
if (self) {
_iTunesApplication = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
NSArray *sources = [_iTunesApplication sources];
for (iTunesSource *source in sources) {
if ([source kind] == iTunesESrcLibrary) {
_iTunesLibrary = source;
break;
}
}
}
return self;
}
- (NSDictionary*) currentTrack {
iTunesTrack* track = _iTunesApplication.currentTrack;
if (!track)
return nil;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: track.name, @"title", nil];
return dict;
}
@end
and the calling swift code:
import Foundation
import Cocoa
class ITunesBridgeSimple {
var iTunesBridgex: ITunesBridgex
init(){
iTunesBridgex = ITunesBridgex()
self.updateFromCurrentTrack()
}
func updateFromCurrentTrack() {
if let track = self.currentTrack {
if let title : AnyObject = track.objectForKey("title"){
println("Current track: \(title)")
}
}
}
}
I suspected that the problem was that the iTunes.h file was not being imported. Therefore it's methods where not being picked up.
So I created a -Bridging-Header.h file.
My Project is name swiftItunesTest. so the -Bridging-Header.h file is named:
swiftItunesTest-Bridging-Header.h
Inside of this I placed the #import "iTunes.h"
line
And in the AppDelegate.swift file
import Cocoa
import Appkit
import ScriptingBridge
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var window: NSWindow
func applicationDidFinishLaunching(aNotification: NSNotification?) {
var iTunes : AnyObject = SBApplication.applicationWithBundleIdentifier("com.apple.iTunes")
iTunes.playpause()
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
The iTunesApplication (iTunes.) now started to pick up the methods/functions
Here is a slightly updated example.
func applicationDidFinishLaunching(aNotification: NSNotification) {
let iTunes : AnyObject = SBApplication(bundleIdentifier: "com.apple.iTunes")!
iTunes.playpause()
guard let currentTrack: AnyObject = iTunes.currentTrack!.name else {
print("No Tracks Playing")
return
}
print("\(currentTrack)")
}
func applicationWillTerminate(aNotification: NSNotification) {
}
I wrote a Python script to generate Scripting Bridge headers and then automatically make a native Swift version. That way you don't have to deal with writing full wrappers or even using a Bridging Header. Also, no worry of Linker Errors because it's completely in Swift. https://github.com/garrett-davidson/SwiftingBridge/
An inelegant way to do work around the issue is to have iTunes
declared as an SBApplication
, then, when you call a function from iTunesApplication
, downcast iTunes
to an AnyObject
:
(iTunes as AnyObject).play()
Note that there is no type-safety if you do this: you could call any function declared in any Objective-C header: it's just not guaranteed to be implemented in a specific class, and thus will crash the program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With