Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift and scriptingbridge object initialization

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.

like image 772
WE-St0r Avatar asked Jun 06 '14 13:06

WE-St0r


4 Answers

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)")
            }
        }
    }
}
like image 193
CMont Avatar answered Nov 20 '22 22:11

CMont


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) {

    }
like image 43
markhunte Avatar answered Nov 20 '22 21:11

markhunte


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/

like image 33
Garrett Avatar answered Nov 20 '22 20:11

Garrett


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.

like image 33
MaddTheSane Avatar answered Nov 20 '22 22:11

MaddTheSane