App Tour – Part 1: Dynamically injecting video into a UIWebView

by Andy

I didn’t intend to kick off this blog with a hefty four part series on a deep iOS topic, but it is fresh in my mind and in the recent implementation of this functionality I encountered several interesting topics that are not well covered elsewhere on the web.

The headline feature in the latest version of my app1 is a tour – a set of pages that describe the underlying concepts of ZiNG! and how to use the app. These pages are a set of HTML documents presented in a UIWebView, and for the actual help I decided to supplement those pages with screencasts. I use videos on an external support center site so it was nice to get double duty out of them.

Incidentally, I create the videos with the Reflection app that uses AirPlay to mirror a device’s screen to a desktop. The app has a built in recorder to capture whatever you do on the device.

Taste ZiNG! is a universal app with slightly different UIs on the iPhone and iPad, which means that any screencasts will vary from device to device. I did not want to embed the videos in the app because that would have bloated the download significantly and I wanted the capability to modify the videos. That meant that I needed, at run-time, to dynamically determine which video to show in which situation, based on device, app version and tour page, and to splice that specific video into the HTML.

Therefore each HTML document contains just the basic text and I inject the appropriate video tags when the page loads. The HTML documents are shipped with the app so that the tour still has content even if the user is offline.

For the first task of determining which video page to serve up, I use a class in Parse pre-populated with data about the video URLs from my video hosting service. Parse is literally a mobile developer’s best friend. I use it all the time to store data in the cloud, without the need to build out a server infrastructure, resulting in huge savings in time, effort and money. Using the Parse SDK to fetch data is super simple:

PFQuery *query = [PFQuery queryWithClassName:@"Videos"];
[query whereKey:@"videoId" equalTo:[NSNumber numberWithInteger:[[item objectForKey:@"videoID"] integerValue]]];
[query whereKey:@"model" equalTo:model];
[query whereKey:@"zingVersion" lessThanOrEqualTo:[NSNumber numberWithInteger:CURRENT_VERSION]];
[query orderByDescending:@"zingVersion"];
// Go get the name of the video
[query getFirstObjectInBackgroundWithBlock:^(PFObject *video, NSError *error) {
if (error) {
// ...
} else {
NSString *videoSource = [video objectForKey:@"videoURL"];
if ([videoSource length] > 0) {
NSString *thumbnail = [video objectForKey:@"thumbnailURL"];
NSString *script = [NSString stringWithFormat:@"createVideo('%@','%@')", thumbnail, videoSource];
// ...
}
}
}];
view raw gistfile1.m hosted with ❤ by GitHub

I instantiate a PFQuery class, set the retrieval parameters and fetch the data in a background thread. A successful result returns a PFObject, which is just a dictionary from which I can extract the columns I am interested in. I use that data to create a little snippet of Javascript, which is part of the second task of adding the video to the HTML document.

An Objective-C class can interact with a UIWebView through the -stringByEvaluatingJavaScriptFromString: method. I invoke this when the UIWebView’s delegate receives a webViewDidFinishLoad: request.

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if (onLoadScript) {
        [webView stringByEvaluatingJavaScriptFromString:onLoadScript];
    }
}

This script invokes a Javascript function with the video URL and poster thumbnail URL as parameters. The definition of that function is included into each of the tour’s HTML documents.

function createVideo( thumbnail, videoSource ) {
var root = document.body
var vid = document.createElement("video")
vid.setAttribute("controls","controls")
vid.setAttribute("poster",thumbnail)
var src = document.createElement("source")
src.setAttribute("src",videoSource)
src.setAttribute("type","video/mp4")
vid.appendChild(src)
root.appendChild(vid)
}
view raw gistfile1.js hosted with ❤ by GitHub

The UIWebView control supports HTML5’s video tag and because this is an iOS device I only need to be concerned with MP4 video.

So now I can dynamically determine which video to show for a particular tour topic, inject that video URL into a HTML document and let iOS handle the video playback.

  • Part 2: Detecting scrolling to the end of a UIWebView page
  • Part 3: Handling full screen video
  • Part 4: Mimicking instance variables for a category
  1. Taste ZiNG! v1.3, available Nov 5, 2012