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