I haven’t had much chance to play with Cocoa in a while and I’ve got an itch to play with it a bit more. I’m actually tackling two things at once, refreshing my Cocoa and playing with the Google GData APIs for Objective-C. I figure why not write it all down as I do it? This will serve to remind me how these things work and maybe help someone else out. As a note, this example is heavily cribbed from the YouTube example that ships with the GData code as such all code is under the same Apache version 2.0 license.

Note that this example is using XCode 3.0. If you’ve got an earlier version of XCode some of these directions maybe different.

So, without further ado, MyTube, the baby steps edition. The goal for MyTube is simple. Get the feed listings from YouTube, display thumbnails and titles for the videos and let the user play the videos. Nothing fancy, and for the first version, no authentication.

Ok, let’s get started.

The first thing you’re going to want to do is grab the GData zip file off of the GData Objective-C Client website. Once you’ve got this in hand, open it in XCode and compile the framework. Once it’s built you can either use the framework in place or copy it to your library path. I had the best luck with copying the framework into my local library path. You can do this in other ways, and the Google examples have a copy script if you want to do it that way. If you want to just stick it in your library path copy the GData.framework directory that was built into your ~/Library/Frameworks directory. My directions that follow will assume you’ve copied the framework into your frameworks directory. If you don’t do this adjust as needed below.

Once the GData framework is installed we’re ready to roll. First we create a new project in XCode. This is as simple as hitting the File -> New Project menu item. I named my project MyTube and that’s what I’ll be referring to and using as my naming convention.

When the project is created we can go ahead and add the GData framework. This is done by expanding the Frameworks section, ctrl-clicking on Linked Frameworks and select Add -> Existing Frameworks. Navigate to your ~/Library/Frameworks/ directory and select GData.framework. Once the framework is linked do a quick apple-R to build the project and make sure it runs.

During my initial fiddling with adding the framework I was getting a strange dyld error about not being able to load an image. My solution was to copy the framework into the ~/Library/Frameworks directory. So, if you didn’t do that and are getting a strange runtime error, try moving the framework and re-adding it.

Now that the groundwork is in place, let’s get on with the actual application development. First, we’ll create an application controller class. Ctrl-click on the Classes directory and select Add -> New File... from the menu. Select Objective-C NSWindowController Subclass from the list and hit next. Enter the name MyTubeAppController as the name of the class and hit finish. You should end up with a .m and a .h file in your classes directory.

Edit the .h file so it’s similar to the following:

#import <Cocoa/Cocoa.h>

@interface MyTubeAppController : NSWindowController {
    IBOutlet NSPopUpButton *feed_type;
}
- (IBAction)grab:(id)sender;
@end

What we’ve done is defining an outlet and action to be used by Interface Building when hooking the GUI to our backend code. The outlet we’ll attach to a popup button we’re going to create and the action we’ll attach to the pressing of a button in the GUI.

Open the MyTubeAppController.m file and edit to so it’s similar to:

#import "MyTubeAppController.h"
#import "GData/GData.h"

@implementation MyTubeAppController
- (void)awakeFromNib {
    NSArray *feedTypes = [NSArray arrayWithObjects:
            @"Most Discussed",
            @"Most Linked",
            @"Most Responded",
            @"Most Viewed",
            @"Top Favorites",
            @"Recently Featured",
            nil];

    /* set the items into the field types combo box */
    [feed_type removeAllItems];
    [feed_type addItemsWithTitles:feedTypes];
    [feed_type selectItemWithTitle:@"Most Viewed"];
}

- (IBAction)grab:(id)sender {
    NSLog(@"GRAB %@", [[feed_type selectedItem] title]);
}
@end

We start out by importing the need GData header files. These will give us access to all of the GData code. We then override the awakeFromNib to setup our popup button. The strings set into the popup are based on the keys we’ll need when doing the YouTube queries. We clear out our popup, set the strings and select the Most Viewed item.

Lastly, we create a simple grab function which just prints the title of the selected popup item when the button is pressed.

Save all of that and double click on the MainMenu.nib file to launch Interface Builder.

In interface builder go to the Objects & Controllers section of the Library and drag the Object block into the MainMenu.nib window. Click on the block and press apple-i to bring up the inspector. Press apple-6 to switch to the identity tab of the inspector. In the class name field enter MyTubeAppController.

With that done drag a popup button and a regular button into the application window. When the items are placed ctrl-click on the controller object we placed earlier. A black popup should appear. Click in the circle beside feed_type and drag to the popup button and release. Click in the circle beside grab and drag to the regular button. We’ve now hooked the interface items in the .nib file to the items defined in our .h file. Hit apple-s to save the nib file.

Switch back to Xcode and press apple-r to build and run the project. You should be able to select items from the popup and when you press the button a log message will appear in the debug console telling you what you selected.

We know the basic app is working so lets actually query some data from YouTube. In order to create the YouTube service I’m going to define a private method inside MyTubeAppController. This is done by adding the following after the initial declarations but before the @implementation entry in MyTubeAppController.m

@interface MyTubeAppController (Private)
- (GDataServiceGoogleYouTube *)youTubeService;
@end

With that out of the way, we can go all the way to the end of the MyTubeAppController.m file, after the @end entry and insert the following: (Yes, this is ripped from the Google example file with minor modifications)

@implementation MyTubeAppController (Private)
- (GDataServiceGoogleYouTube *)youTubeService {
    static GDataServiceGoogleYouTube *service = nil;

    if (!service) {
        service = [[GDataServiceGoogleYouTube alloc] init];
        [service setUserAgent:@"MyTube-1.0"];
        [service setShouldCacheDatedData:YES];

        /* this is where we'd set the username/password if we accepted one */
        [service setUserCredentialsWithUsername:nil password:nil];
    }
    return service;
}
@end

What are we doing here you ask? Well, it’s actually pretty simple we’re creating a static GDataServiceGoogleYouTube object, static because we only want one to exist, and creating that service if needed. We set the user agent string to something unique for our application, in this case MyTube-1.0 and we turn on the data caching feature.

I’m not setting any credentials into the object as I’m just doing stuff anonymously at the moment (I don’t actually have a YouTube account). You can refer to the Google example on how to set the username and password.

With the ability to create our service we’re going to modify our grab function to pull down the feed items that are selected. Modify grab to be similar to the following:

- (IBAction)grab:(id)sender
{
    GDataServiceGoogleYouTube *service = [self youTubeService];
    GDataServiceTicket *ticket;

    NSString *feedName = [[feed_type selectedItem] title];
    feedName = [feedName lowercaseString];
    feedName = [feedName stringByReplacingOccurrencesOfString:@" " withString:@"_"];

    NSURL *feedURL = [GDataServiceGoogleYouTube youTubeURLForFeedID:feedName];
    ticket = [service fetchYouTubeFeedWithURL:feedURL
                                     delegate:self
                            didFinishSelector:@selector(entryListFetchTicket:finishedWithFeed:)
                              didFailSelector:@selector(entryListFetchTicket:failedWithError:)];
}

We’re using the previously defined youTubeService call to get our service. We then need to figure out the feed we want to query. As I mentioned, the names I’m using in awakeFromNib while similar, aren’t quite what YouTube expects. We need to modify the strings to be all lower case and replace any space ( ) characters with an underscore (_).

With the corrected feed name we can then create a feed URL. In this case we’re just using the youTubeURLForFeedID as we don’t have a userID to query with. If you’re doing authentication stuff refer to the Google example to see how to use the youTubeURLForUserID:(NSString *)username userFeedID:(NSString *)feedID function.

Using the service object we create we can now make our query to YouTube. This is done with the fetchYouTubeFeedWithURL:(NSURL *)url delegate:(id)delegate didFinishSelector:selector didFailSelector:selector this will return a GDataServiceTicket object which, if we saved it, could be used to cancel our query or other things.

All we’ve got left to do in this leg of the trip is to define the two selector functions defined above. Add this to the MyTubeAppController.m file after the grab function.

- (void)entryListFetchTicket:(GDataServiceTicket *)ticket finishedWithFeed:(GDataFeedBase *)feed {
    GDataFeedYouTubeVideo *vfeed = (GDataFeedYouTubeVideo *)feed;
    int i;

    for (i = 0; i < [[vfeed entries] count]; i++) {
        GDataEntryBase *entry = [[vfeed entries] objectAtIndex:i];
        NSLog(@"Title: %@", [[entry title] stringValue]);
    }
}

- (void)entryListFetchTicket:(GDataServiceTicket *)ticket failedWithError:(NSError *)error {
    NSLog(@"Error %@", error);
}

As you can see, we aren’t doing anything fancy here. In the finishedWithFeed case we cast the provided feed to a GDataFeedYouTubeVideo object. Using this object we then iterate over the entries and print the title of each entry to the console. In the failedWithError case we just print out the error message to the console.

That’s it. If you hit apple-r to build and run the project you should be able to select items in the popup box and press the grab button to see a list of video titles appear in the console window.

Congratulations, you’ve just made your first query to YouTube from Objective-C.

That’s the end of this installment. I need to get some sleep. Hopefully in the next few days I’ll have some time to write part two which will, hopefully, take the videos and draw thumbnail images into our window so we can see pretty pictures.

Full listing for MyTubeAppController.m follows:

#import "MyTubeAppController.h"
#import "GData/GData.h"

@interface MyTubeAppController (Private)
- (GDataServiceGoogleYouTube *)youTubeService;
@end

@implementation MyTubeAppController
- (void)awakeFromNib {
    NSArray *feedTypes = [NSArray arrayWithObjects:
            @"Most Discussed",
            @"Most Linked",
            @"Most Responded",
            @"Most Viewed",
            @"Top Favorites",
            @"Recently Featured",
            nil];

    /* set the items into the field types combo box */
    [feed_type removeAllItems];
    [feed_type addItemsWithTitles:feedTypes];
    [feed_type selectItemWithTitle:@"Most Viewed"];
}

- (IBAction)grab:(id)sender {
    GDataServiceGoogleYouTube *service = [self youTubeService];
    GDataServiceTicket *ticket;

    NSString *feedName = [[feed_type selectedItem] title];
    feedName = [feedName lowercaseString];
    feedName = [feedName stringByReplacingOccurrencesOfString:@" " withString:@"_"];

    NSURL *feedURL = [GDataServiceGoogleYouTube youTubeURLForFeedID:feedName];
    ticket = [service fetchYouTubeFeedWithURL:feedURL
                                     delegate:self
                            didFinishSelector:@selector(entryListFetchTicket:finishedWithFeed:)
                              didFailSelector:@selector(entryListFetchTicket:failedWithError:)];
}

- (void)entryListFetchTicket:(GDataServiceTicket *)ticket finishedWithFeed:(GDataFeedBase *)feed {
    GDataFeedYouTubeVideo *vfeed = (GDataFeedYouTubeVideo *)feed;
    int i;

    for (i = 0; i < [[vfeed entries] count]; i++) {
        GDataEntryBase *entry = [[vfeed entries] objectAtIndex:i];
        NSLog(@"Title: %@", [[entry title] stringValue]);
    }
}

- (void)entryListFetchTicket:(GDataServiceTicket *)ticket failedWithError:(NSError *)error {
    NSLog(@"Error %@", error);
}
@end

@implementation MyTubeAppController (Private)
- (GDataServiceGoogleYouTube *)youTubeService {
    static GDataServiceGoogleYouTube *service = nil;

    if (!service) {
        service = [[GDataServiceGoogleYouTube alloc] init];
        [service setUserAgent:@"MyTube-1.0"];
        [service setShouldCacheDatedData:YES];

        /* this is where we'd set the username/password if we accepted one */
        [service setUserCredentialsWithUsername:nil password:nil];
    }
    return service;
}
@end