Note: I’ve created a new iOS5 friendly version of this template/tutorial.

If you are a Web and iOS developer like myself, the temptation of blending these two things is all too great. One disadvantage of making web apps that run straight from Safari, however, is that they can’t harness many of the device’s awesome functionalities. The good news is that, with a small foundation of objective-C code, you can make a native iOS app that takes full advantage.

This article is about blending all the pieces together – HTML, CSS, Javascript and Objective-C in order to develop a foundation for any future native web app with the device Camera functionality added to get you started.

Let’s briefly go over the steps of what we will do:

  • Review the demo HTML, CSS, and Javascript components we will add to the XCode Project
  • Start a new XCode Project using the single view-based app template.
  • Add a UIWebView to our View Controller in Interface Builder and make one connection
  • Load the HTML, CSS and Javascript files in to the Web View
  • Create a UIWebViewDelegate which listens for link clicks and dispatches accordingly
  • Use the UIImagePicker to take a picture with the phone
  • Encode the image to Base64 and pass it back through Javascript
And with that, let’s get started! You can download the sample code here and follow along. I am making the assumption that you are familiar with the basics of these various languages and will spend time focussing on bring it all together rather than spelling out each line of code

The HTML, CSS and Javascript

Below I have embedded the HTML View that we will be adding in to our XCode project, give it a whirl! You should notice that two of the links are seemingly dead – these will be linked with Objective-C. This basic view is made up of three files – index.html, styles.css and scripts.js and also the jQuery library for convenience.

jQuery Alert

Objective-C Alert

Take a picture

 


index.html

<!doctype html>
<head>
    <meta charset="utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1">

      <link rel="stylesheet" href="style.css">

      <script src="jquery-1.6.2.min.js"></script>
      <script defer src="script.js"></script>
</head>
<body id="body">
  <div id="container">

    <div id="main" role="main">
        <a href="/" class="js">jQuery Alert</a><br />
        <a href="objc://message">Objective-C Alert</a><br />
        <a href="objc://takePicture">Take a picture</a><br />
    </div><!-- #main -->

    <img id="testImage" src="iphonebattery.jpeg" />

  </div><!-- #container -->
</body>
</html>

styles.css

body {
    padding: 10px;
}

#main a{
    display: block;
    width: 90%;
    min-height: 24px;
    color: #FFF;
    background-color: #234234;
    border-radius: 5px;
    text-align: center;
    padding: 10px;
    text-decoration: none;
    text-transform: uppercase;
    font-weight: bold;
    -webkit-touch-callout: none;
   /* -webkit-user-select: none; */
}

#main a:hover{
    background-color: #432432;
}

#testImage{
    border: 1px solid #CCC;
    width: 100%;
    height: 66%;
}

scripts.js

$(document).ready(function(){ 

    $("a.js").click(function(){ 
                    alert("You clicked on a link that activates javascript"); 
                    });
              
                  
});


function processImage( img )
{
    $('#testImage').remove();
    $('#body').append( '' );
}

We have set up a basic html document structure and 3 links in the HTML code. We then styled them in CSS to fit the dimensions of the screen, nothing very fancy. One noteworthy line in CSS is an iOS specific property that disables the callout when you hold your finger on a link:

  -webkit-touch-callout: none;

Finally in the javascript, we take advantage of the jQuery library included and setup a listener for our “Jquery Alert” button as well as a function called processImage() which can take a base64 string of an Image and insert it where the battery image is currently showing.

The XCode Project

Now we can move on to our XCode project. You can build your own or follow along in the sample code.
I’ve used a simple View Based Application. First, in our WebViewController.h we should first declare an outlet for a UIWebView.

WebViewController.h

@interface WebViewController : UIViewController
{
    IBOutlet UIWebView *webView;
}

@property(nonatomic, retain) IBOutlet UIWebView *webView;

@end

Next, in the Interface builder we drag over a UIWebView on top of our UIView and make a connection between the IBOutlet and the UIWebView. This image basically demonstrates it all:



Out of Interface Builder and Into the Fire

With our connection set up we are now ready to NEVER TOUCH INTERFACE BUILDER AGAIN. Maybe that’s a little harsh, but you get the point. Now we just need to get our Web View to load our web files. We can do this all of inside of viewDidLoad.

Part Of WebViewController.m

#pragma mark - WebTemplateViewController

- (void)viewDidLoad
{
      [super viewDidLoad];
   
    //First we load up the index.html file
    NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSData *htmlData = [NSData dataWithContentsOfFile:path];
    
    //Next we need to set up a proper base URL for our files 
    NSString *resourceURL = [[NSBundle mainBundle] resourcePath];
    NSLog(@"%@", resourceURL); // <- the URL in the raw still needs some cleaning
    // Need to be double-slashes to work correctly with UIWebView, so change all "/" to "//"
    resourceURL = [resourceURL stringByReplacingOccurrencesOfString:@"/" withString:@"//"];
    // Also need to replace all spaces with "%20"
    resourceURL = [resourceURL stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
    //And make a proper URL
    NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"file:/%@//",resourceURL]];

    //Finally let's load up the html data and passthe Base URL for the CSS and Javascript files
    [webView loadData:htmlData MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:baseURL];
    
    //The delegate is where we will handle notifications from the UIWebView
    webView.delegate = [[WebViewDelegate alloc] init];

}

I've commented through this portion pretty well but to summarize - we only need to load the html page and then set a base url for all the additional files that the html page is requesting. Lastly we prepare our WebViewDelegate - the class I want to talk about next.

The Web View Delegate

While we are only going to be interested in one of the 4 callbacks in the UIWebViewDelegate Protocol, I wanted to share it with you as a whole nonetheless so that you can get a feel for what we can listen for:

UIWebViewDelegate Protocol

#pragma mark - WebViewDelegate Protocol

@protocol UIWebViewDelegate <NSObject>

@optional
//we want this one
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//the other three
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;

@end

So what does this one call back do for us? Well basically, the UIWebView asks the delegate for permission to navigate anywhere. If you return FALSE, the link won't be followed, if you return TRUE something amazing happens. Let's take a look at how we handle that now:

WebViewDelegate.h

@interface WebViewDelegate : NSObject 
<UIWebViewDelegate,
UIImagePickerControllerDelegate> //we will also be using the UIImagePicker in this class
{
    UIWebView *webView;
}

@end

WebViewDelegate.m

@implementation WebViewDelegate

- (BOOL)webView:(UIWebView*)webViewRef shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
     //we are listening out for links being click
    if (navigationType == UIWebViewNavigationTypeLinkClicked) 
    {
        
        NSURL *URL = [request URL]; //Get the URL
        
        //The [URL scheme] is the "http" or "ftp" portion, for example 
        //so let's make one up that isn't used at all -> "objc"
        //
        if ( [[URL scheme] isEqualToString:@"objc"] ) {
            
            //hold a reference to this webview for calling back to the webview later
            webView = webViewRef;
            
            //The [URL host] is the next part of the link
            //so we can use that like a selector
            SEL method = NSSelectorFromString( [URL host] );
            
            if ([self respondsToSelector:method])
            {
                // if you delay the method just slightly, 
                // you can get the <a> button to change color to
                // your CSS :hover state before the method takes action
                [self performSelector:method withObject:nil afterDelay:0.1f];
            }
            
            return NO;
        }
        
    }
    
   return YES;
}

-(void)message
{
    //Showing a basic pop up alert

	    NSString *message = [NSString stringWithString:
	                         @"You clicked on this test link" ];

	    UIAlertView *alert = [[UIAlertView alloc] 
	                          initWithTitle:@"Message From OBJ-C" 
	                          message:message 
	                          delegate:nil 
	                          cancelButtonTitle:@"OK" 
	                          otherButtonTitles:nil, nil];

	    [alert show];
	    [alert release];
}

Before going into the next portion of this file, let's review these two functions. As I noted in the code above, we are taking advantage of [url scheme] in order to distinguish objective-c links from other link types such as "http://" links. Next we use [url host] to define the name of the selector we want to run.

This is an area that you can get creative with as there isn't really a right or wrong. If you are familiar with Web MVC frameworks like CodeIgniter of CakePHP (sorry, I only know PHP frameworks) then you might want to take a "/controller/action/parameters..." style approach.

With that there is one more important thing we must do - Use some of the device's functionality and pass that information back to our WebView.

WebViewDelegate.m continued

-(void)takePicture
{
    // Set the UIImagePicker to Camera and self as the delegate 
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
	imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
	imagePicker.delegate = self;
    
    // Present the image picker
    id delegate = [[UIApplication sharedApplication] delegate];
	[[delegate viewController] presentModalViewController:imagePicker animated:YES];
    
    [imagePicker release];
    
}

#pragma mark - Image Picker
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    //Get the Image
	UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    
    //flatten it to NSData as a JPEG, low quality
    NSData *flatImage = UIImageJPEGRepresentation(image, 0.1f);
    
    // convert NSData to a base 64 encoded string
    // NSData+Base64 Category provided by Matt Gallagher
    // http://cocoawithlove.com/2009/06/base64-encoding-options-on-mac-and.html
    //
    NSString *image64 = [flatImage base64EncodedString];
    
    //process the image in javascript to be added to the page
    NSString *js = [NSString stringWithFormat: @"processImage('data:image/jpeg;base64,%@')", image64];
    [webView stringByEvaluatingJavaScriptFromString:js];
    
    //dismiss the image picker
    [picker dismissModalViewControllerAnimated:YES];
	
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    
    //cancel was hit inside of the camera view
    [picker dismissModalViewControllerAnimated:YES];
}
@end

And with that we have our last methods to bring it all together. If you recall, we set up objc://takePicture in the HTML view which runs our first method, setting up the UIImage Picker. Once the camera launches, it is out of our hands until the user cancels or confirms an image. When this happens, we take the image, convert it to a JPEG file and Base64 Encode it. You will see that I have used a Base64 Category built by Matt Gallagher . My only caution is that the one provided directly by him adds line breaks (\n) occasionally which seems to break the javascript. Therefore, in my sample code provided I have commented out the lines in his code that created these breaks.

Lastly we create a NSString of Javascript and tell our webview to execute it. As you can see, we are running the function processImage()we set up for ourselves much earlier.

In Closing

I hope that this is a useful tutorial and template for you. Feel free to download the sample code if you haven't already. If you have any questions or would like to see more tutorials like this in the future, please let me know.

Post filed under Blog and tagged , .

  • http://twitter.com/snrkysnrky snrky

    Thank you for the actual code.

  • Skinnyuser

    There is an easier was to make use of iOS webview. Online services like Seattle Clouds provide its users with a code-free online editor. If you know html/css you will of course do a slightly better job there but it is really not necessary to have any programming skills.

    http://seattleclouds.com

  • Anonymous

    This is not really an appropriate comment for a developer blog targeting the code-savvy. Furthermore please don’t promote businesses on my website. I’m removing this comment.

  • http://twitter.com/Bigheadedegg Matt

    Hi,

    This is excellent thanks! How can I pass parameters down the URL and is there a way to pass a JSON object in?

    Cheers!

  • Anonymous

    Hi Matt,

    By parameters down the URL what exactly do you mean? You can break the URL into components and act accordingly but there’s no right or wrong way exactly..

    Re: passing javascript between them, try this:
    https://github.com/marcuswestin/WebViewJavascriptBridge

  • Desiree

    Hi Kyle,

    thank you so much for this tutorial and sample code. I’m totally new to programming, and this information has been extremely helpful to me in understanding more about how to pass calls between objective-c, javascript, etc.

    I did run into one problem though, and was wondering if you knew why I was having trouble. In attempting to update your sample code to use ARC, I got all the typical warnings, which were easy updates to make, but then there’s one issue that I can’t seem to rectify.

    In the WebViewDelegate.m file, in the ViewDidLoad statement block:

    //The delegate is where we will handle notifications from the UIWebView
    webView.delegate = [[WebViewDelegate alloc] init];

    results in this warning:

    Assigning retained object to unsafe property; object will be released after assignment

    I’m not sure which object they’re referring to, and how to update it to ARC-acceptable code. I’ve read a bit about retaining v strong, weak, etc., but I just can’t seem to get my head around this.

    Any help would be greatly appreciated as I’ve been banging my head against this particular wall for a few days now.

    Thanks again for the great info!

    Best,
    Desiree

  • Anonymous

    Hi Desiree,

    Ok, here’s the issue to the best of my knowledge:

    The problem is that the WebViewDelegate isn’t going to be retained under the new rules of ARC because there is no strong reference to it. When we assign it as the delegate, this is a weak reference (assign, in non-ARC terms).

    Under the old rules, “alloc/init” was enough to retain it once, and we were expected to manually call “release” later in the code (like when the webview is deallocated).

    Under the new rules, you aren’t allowed to manually call “release” later and the “alloc/init” isn’t considered a retain on it’s own any more.

    In order to solve your problem, try this:

    First in WebViewController.h add this:

    WebViewDelegate *webViewDelegate;

    then this:

    @property (nonatomic, strong) WebViewDelegate *webViewDelegate;

    In WebViewController.m near the top:

    @synthesize webViewDelegate;

    Next change to this in viewDidLoad:

    //The delegate is where we will handle notifications from the UIWebView
    self.webViewDelegate = [[WebViewDelegate alloc] init];
    webView.delegate = webViewDelegate;

    FINALLY, change -(void)dealloc to this:

    -(void)dealloc
    {
    self.webViewDelegate = nil;
    }

    Voila, now you are holding a strong reference to the delegate in your view controller!

    Please note that there are other things required to making ARC work that I didn’t mention here. Off the top of my head, you’ll need to update the ‘webView’ property as well to be in line with ARC.

  • Desiree

    Thank you so much for such a quick and thorough response!! I haven’t had a chance to try these changes yet, but I can’t wait to try them this evening. I really appreciate the help.

    Best,
    Desiree

  • mungbeans

    Hello, why is the web view delegate being set like this:

    webView.delegate = [[WebViewDelegate alloc] init];

    and not

    webView.delegate = self;

  • mungbeans

    To clarify – why create a delegate class inheriting from UIWebViewDelegate and then allocate an object of that type. Why doesn’t the WebViewController inherit from UIWebViewDelegate?

  • Anonymous

    Good question. To clarify – you are asking why the WebViewController doesn’t implement the UIWebViewDelegate protocol directly and why I instead wrote a separate class. There isn’t an inherit happening there.

    The short answer is neither way is wrong and I did it like this as a demonstration to better separate the concepts. Depending on your app’s complexity you might prefer one way or the other. For something simple, your way makes most sense. If I wrote this again, I would probably do it that way too.

  • Developer

    This is an awesome article. Thank you for the knowledge sharing session! You’re the best…

    A few things worth noting:
    1) Some weird activity was going down over here. I had to modify my project settings and made sure the javascript files were added in the “Build Phase” -> “Copy Bundle Resources” section. I don’t know why XCode omitted the *.js files. Maybe *.js is not recognized. :-/

    2) Don’t forget to inherit from the “UINavigationControllerDelegate”. I received a warning during my walkthrough.
    3) I actually chose to include all the source code under one object. That’s how I roll. ;-)

    Thank you again! You’re the best!!

  • Anonymous

    Hey “Developer”,

    Thanks for the kind words! It sounds like you might have copied some of this template’s code into a project of your own which might explain the issues.

    1) Xcode evaluates .js files as code to be compiled, which isn’t what we want. This is why you needed to put it in “Copy Bundle Resources” instead in your project settings

    2) There wasn’t a UINavigationController in my original project, just a UIViewController (WebViewController subclassed it), but yes it is good practice to always be clear about your delegates

    3) It is definitely possible to put it all in one file BUT be warned that now you have functions that could potentially be called from HTML which you don’t actually want to. Putting it in a separate delegate means a better separation of concerns between the iPhone code you want private and the functions you want to expose

    Cheers!

  • Beginner

    I am creating a iPad application and I downloaded your code and tried to build and run but got the bellow error. How can I rectify this ?

    error: There is no SDK with the name or path ‘iphoneos’

  • Guilherme Rogieri

    Awesome post!! Tks for this!! =]

  • Wwwwwpwd

    kapil

  • Anna Harris

    Thanks for sharing information of new iOS5 version useful tutorial and template for beginners.

  • good