0

A Native iOS Web App Tutorial

iPhone 4
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.



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( '<img id="testImage" src="' + img + '" />' );
}

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 in to 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 use 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.

1

Thank you Steve


Today we, like many others, are saddened by the loss of one of our generations greatest visionaries, Steve Jobs. We are but a speck in the vast universe of creative folk hoping to do something today that will make tomorrow better. Yet it is no exaggeration to stay that without Steve Jobs, we all would not be in the position we are today.

It is our belief the best way to honour him is to pause briefly to reflect on his legacy and lessons, then press on, work passionately, enjoy life and be the best creative technologists we can be.

Thank you Steve.

0

Write .less .css


We’ve been using LESS, a dynamic stylesheet language for a while now and wanted to share our thoughts and a few tips.
continue reading

3

Welcome Annabelle Yoon!

We’re happy to welcome our newest team member, Annabelle Yoon, all the way from New Zealand.

Annabelle brings valuable conceptual and design skills to the role of Designer, with experience in strategic brainstorming, campaign development and creative execution on award winning campaigns while at Rapport in Auckland, NZ.

We’re extremely happy to have Annabelle on board, and look forward to helping her acclimatize to our fair city.

Feel free to leave a comment to welcome Annabelle, Kyle’s grandmother this means you…

0

Toronto Budget Data

Toronto Budget Data

We’re excited to release the Toronto Budget Data REST API and example data visualizations designed around it. The API provides easier access to the city’s current budget data, made available through the Toronto Open Data initiative.
continue reading

0

Flashpoint: Training Day Nominated for a Gemini


We’re happy to announce that Flashpoint: Training Day has received a nomination for the 26th Annual Gemini Awards in the ‘Best Cross-Platform – Non-Fiction’ category.
continue reading

Initial Thoughts on Google+

The team here at Uproot are a week-deep into using Google Plus so we thought we’d share our initial impressions.
continue reading

0

Hiring: Designer


We’re seeking a talented Designer to join our team. In this wide-ranging role, you’ll be tasked with solving key user experience challenges using a confluence of skills including art direction, information architecture, research and prototyping.
continue reading

4

Less Framework Grid – OmniGraffle Stencil

Less Framework Grid - OmniGraffle Stencil
You may have noticed Responsive Web design, the practice of using CSS media-queries to style content appropriately for device and context, is becoming more prevalent. In fact, at the time of writing this post, MediaQueri.es, a blog dedicated to showcasing Responsive Web designs was a trending post on Twitter.

Among user experience practitioners, this adoption is a welcome movement as we move into an era where digital consumption is happening on a growing range of devices – from smart phones, to tablets to even large area displays like televisions.
continue reading

0

Critical Technology: VitalHub


We often talk about creative technology as the impetus to solving new challenges for industries like advertising, publishing and finance. However, as folks with friends and family who’ve recently required serious medical care over the last year, we wanted to pause talk about what we call ‘critical technology’ – the solutions that will help healthcare providers and patients get the best medical treatment possible through elegant Web-enabled, mobile systems.
continue reading