Swift and scriptingbridge object initialization
Asked Answered
S

4

6

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.

Simonsimona answered 6/6, 2014 at 13:8 Comment(6)
When you do println("\(iTunes.className)") it returns "ITunesApplication"Aldarcy
Compiler error - "AnyObject does not have a member named className"Simonsimona
Yes. I saw that in my tests. I was pointing out that although it gives that error it is classed as a ITunesApplication. can I also suggest you add more detail to your question. about what you have tried to resolve this and how you are importing the iTunes.h file etc. I know I for one was expecting xcode to offer to add it to a header import file but I never got one.Aldarcy
I checked whether the object responds to the challenge iTunes PlayerState. It returns a number. At the same time when I want to refer to the classes, which are denoted by iTunes.h @class, there is a compilation error. It is very strange.Simonsimona
Please add the code to your answer you are getting a response with and the code you are getting the error. This helps anyone who wants to look at this to not guess what you are doing. This will encourage answers.Aldarcy
Updated my answer slighty. missed named the Project name should be swiftItunesTestAldarcy
K
7

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)")
            }
        }
    }
}
Kobayashi answered 14/6, 2014 at 0:9 Comment(0)
A
3

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

    }
Aldarcy answered 10/6, 2014 at 17:25 Comment(4)
@jQwierdy this is an old post and swift has changed since, so I would not be surprised if it did throw an error now but at the time of posting all worked as I state. I have not played much with ScriptingBridge since. Mainly because I had no need and also because it was such a pain to use for all the reasons this post originated.Aldarcy
@jQwierdy All I did to get this to work again was change the iTunes Var line to : ` let iTunes : AnyObject = SBApplication(bundleIdentifier: "com.apple.iTunes")!`Aldarcy
You're right, I made a stupid comment - I'm sorry about that. Stupid of me, I didn't full grasp the meaning of AnyObject being ObjC's ID type - because that means it's dynamically typed. Any my compiler threw an error but it was for a different reason. Sorry about that. Your answer is good.Marmolada
@jQwierdy , no problem. Been there myselfAldarcy
S
3

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/

Stonge answered 27/6, 2015 at 17:39 Comment(0)
G
1

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.

Great answered 25/1, 2016 at 19:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.