How to add videos to the iPhone Simulator

February 24th, 2010

When you’re hacking away writing a video upload script for the iPhone, you usually realize that there’s no way to test your app on the Simulator. Apple didn’t really provide a way to get videos into the Simulator. That’s fine, you can always test on the device; unless you don’t have a 3GS!

Thankfully, there’s an easy fix that I figured out a little while ago. To manually add “/video1.mp4″ to the Simulator, you just need to copy it to a certain place on your hard drive:


$ cd ~/Library/Application\ Support/iPhone\ Simulator/User/Media/DCIM/100APPLE/
$ ls
IMG_0001.JPG	IMG_0002.JPG


This directory might be empty (or even missing, in which case you'll have to create it). What you want to do is copy your video into this folder with a specific filename. In this case:


$ cp /video1.mp4 ./VID_0003.MOV


So, just name it VID_<Incremented number>.MOV. The Simulator is pretty picky about file types: it will try to load any given video file, but when you actually get to the "Video Trimming" screen, it will freeze up if the file isn't the correct encoding (can't be avc1 encoded). Check the terminal for output errors.

Hope this helps someone, would've saved me a lot of time!
-Joe

DataDownloader – A Simple HTTP Class

February 20th, 2010

I’ve been writing apps for a long time, and in the beginning, grabbing any file from the web (or simply making an HTTP request) asynchronously was always a pain in the ass. I had to set myself up as a NSURLConnectionDelegate, and keep track of the data being downloaded in the background. Then there were a couple other delegate methods required to handle various errors. Eventually I wisened up and started making a class, DataDownloader, that does all the work for me, and only requires that I implement one delegate method.

To use it, just make yourself a DataDownloaderDelegate, which entails implementing only one delegate method, dataLoaded. Then, create a DataDownloader and call one of its init methods (there are methods for GET requests, POST requests, and image upload requests), setting yourself as the delegate. If an error occurs, dataLoaded will be called with a value of null.

The interface file for DataDownloader looks like this:


#import <Foundation/Foundation.h>

@protocol DataDownloaderDelegate
- (void)dataLoaded:(NSData *)d tag:(int)t;
@end

@interface DataDownloader : NSObject {
	NSMutableData *data;
	int tag;
	id delegate;
	NSURLConnection *conn;
}

- (void)downloadURL:(NSURL *)url withDelegate:(id)del tag:(int)t;
- (void)downloadURL:(NSURL *)url postData:(NSDictionary *)postData withDelegate:(id)del tag:(int)t;
- (void)downloadURL:(NSURL *)url postData:(NSDictionary *)postData postImage:(UIImage *)img
 	imgName:(NSString *)imgName withDelegate:(id)del tag:(int)t;
- (void)cancel;

@end

You can download DataDownloader here:

DataDownloader.h DataDownloader.m

I am releasing this source with no license, do whatever you want with it.

- Joe

TuneMenu – See the Current iTunes Song in your Menu!

November 10th, 2009

TuneMenu a nice little MenuItem that I wrote that simply displays the current song in the OS X menu bar. You can download it here It also has a couple menu features:

As you can see, clicking the MenuItem opens a submenu that shows the album name, as well as a “Lookup Lyrics” item, which will pull up a new browser tab and point it towards the corresponding lyricwiki.org page. This works most of the time, depending on how cleanly you’ve named your songs.

You can also customize the font, although I can’t say I’m too proud of my methods. To change font settings, you have to open up your Terminal and run any of these commands:

$ defaults write com.silentmac.TuneMenu font-name Arial
$ defaults write com.silentmac.TuneMenu font-size 11
$ defaults write com.silentmac.TuneMenu font-bold TRUE


I prefer the font Cambria, size 10, bolded.

So why did I write it? I love iTunes, but I usually find myself paging back and forth in Expose between applications to pull iTunes to the front just to check the name of the current song. When you have 8+ applications running at the same time, this can be somewhat of a pain (especially if you accidentally pull Photoshop to the front, which has a 100mb file loaded up). I looked around to find iTunes notifiers for Mac - I didn't want anything with Growl (having Growl constantly attack me would just force me to stop listening to music). After a bit of looking around, I found iTunes Current Song Menu, which sounded perfect, but, alas, was shareware that cost $20. I didn't want to pay $20 for a simple little script - how hard could it be - so I busted out XCode and came up with this an hour later.

If you're interested, the source can be found here.

Joe

CSV to sqlite3 Cmd Line Converter

November 3rd, 2009

Every now and then I have an iPhone project that requires me to search through a large amount of local data. Luckily, Apple makes the sqlite3 library available on the phone, so all I have to do is create a compatible sqlite3 database, and I can then search through that in my app. This is much easier said than done.

In the past, I usually find myself parsing a CSV or some other simple formatted file through a custom script that then directs the output to a database I had already created. This works, but is not efficient. Writing a script anytime I need to populate a database is not optimal, so this time I decided to create something more general.

I created a script (source available here, and an OS X binary available here) that takes in a pre-made CSV and pre-made sqlite3 database file, and then populates the database. Sample usage looks like:

$ ./csv2sqlite3 upc.sqlite stores upc.csv

Where upc.sqlite is the database file, ’stores’ is our table name, and upc.csv is the CSV file. Of course, you have to have a pre-made database (in this case, upc.sqlite) before you run it. Creating a database is very easy. Just hop into the directory you want the file saved in, then run:

$ sqlite3 database-name.sqlite
SQLite version 3.6.14.2
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> CREATE TABLE stores (id varchar(10), name varchar(150), state varchar(3));
sqlite> .exit


Make sure that the table has the same structure as the CSV (same amount of fields).

The biggest limitation to this script right now is that it only works for databases where all fields are varchars (no floats or ints I'm afraid). You'll have to customize the script to get this to work for you.

Well, that's it for now. Hope this helps someone

Joe

Embedded Youtube Videos in an iPhone App

October 12th, 2009

About a week ago, a client asked me to do a project that involved embedding a YouTube player inside of an iPhone app, so that the video would play, and then control would be returned to the app. Hesitantly, I searched for a bit before getting back to him. I found this entry over at the YouTube API blog, which shows you how to add YouTube videos to your app by embedding them as a webpage inside of a UIWebView inside your app.

This method works, but the downside is that you have a big ugly YouTube frame/link in your UIWebView:

The client didn’t want this, so I was forced to look for a workaround. Initially, I tried to hide this webview and simply reroute touches to the device on to this specific view. No dice. I tried executing Javascript, calling the “onClick()” handlers that belonged to the embedded object. Still no dice. Weird. I executed some more Javascript to print out all DOM objects on the page, and, surprisingly enough, the <embed> and <object> objects did not exist.

The only possibility left is that UIWebView, upon finding an embedded YouTube video, replaces it with another subclass of UIView inside of the scroll view. Luckily, every UIView provides us open access to its subviews and superviews, so there is no way for Apple to hide the view structure. So I wrote a quick little function that recursively looks through a view’s contents and returns the results as an NSString, to be NSLog’d:

- (NSString *)showSubviews:(UIView *)view tabs:(NSString *)tabs {
	if (!tabs) tabs = @"";
	NSString *currStr = tabs;
	currStr = [NSString stringWithFormat:@"%@%@\n", tabs, [view class], nil];

	if (view.subviews && [view.subviews count] > 0) {
		tabs = [tabs stringByAppendingString:@"\t"];
		for (UIView *subview in view.subviews) {
			currStr = [currStr stringByAppendingString:[self showSubviews:subview tabs:tabs]];
		}
	}

	return currStr;
}

And the output:

UIWebView
	UIScroller
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIImageView
		UIWebDocumentView
			YouTubePlugInView
				UIImageView
				UIImageView
				UIView
				UIView
				UIView
				UIView
				UIButton
					UIImageView

Aha! A YouTubePlugInView! No wonder sending clicks to the webview via Javascript wasn't working - the Youtube player was a native class (not just something drawn on the screen by the UIWebDocumentView). Also, the YouTubePlugInView contains a UIButton - most likely the "play" button you see. Since we have a reference to the button, we don't even need to mess with touch events - we can just call [button sendActionsForControlEvents:UIControlEventTouchUpInside]; and the corresponding code will execute. Genius.

So the final solution looks something like this:

- (void)awakeFromNib {
	webView.delegate = self;
	NSString *htmlString = [NSString stringWithFormat:[NSString stringWithContentsOfFile:[[NSBundle mainBundle]
		pathForResource:@"YouTubeTemplate" ofType:@"txt"]], @"b85hn8rJvgw", @"b85hn8rJvgw", nil];
	[webView loadHTMLString:htmlString baseURL:[NSURL URLWithString:@"http://youtube.com"]];
}

- (UIButton *)findButtonInView:(UIView *)view {
	UIButton *button = nil;

	if ([view isMemberOfClass:[UIButton class]]) {
		return (UIButton *)view;
	}

	if (view.subviews && [view.subviews count] > 0) {
		for (UIView *subview in view.subviews) {
			button = [self findButtonInView:subview];
			if (button) return button;
		}
	}

	return button;
}

- (void)webViewDidFinishLoad:(UIWebView *)_webView {
	UIButton *b = [self findButtonInView:_webView];
	[b sendActionsForControlEvents:UIControlEventTouchUpInside];
}

Where YouTubeTemplate.txt is a local text file containing:

<pre><code><html><head>
<meta name = "viewport" content = "initial-scale = 1.0, user-scalable = no, width = 212"/></head>
<body style="background:#F00;margin-top:0px;margin-left:0px">
<div><object width="212" height="172">
<param name="movie" value="http://www.youtube.com/v/%@&f=gdata_videos"></param>
<param name="wmode" value="transparent"></param>
<embed src="http://www.youtube.com/v/%@&f=gdata_videos"
type="application/x-shockwave-flash" wmode="transparent" width="212" height="172"></embed>
</object></div></body></html></pre></code>

The app requests the webview to load the contents of this file (replacing the %@'s with the video ID), waits for the load to finish, then finds the button in the YouTubePlugInView, and triggers its UITouchUpInsideEvent. And after that, the video will play in an embedded fashion.

Pretty tough stuff.
Joe