Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iPhone

Building Simple Reader Application for iPhone based on UIWebView

4.81/5 (46 votes)
30 Jun 2010CPOL10 min read 166.2K   3.7K  
Learning the basic of developing application on iPhone by developing simple reader app


Table of Contents 

  1. Table Of Content 
  2. Introduction 
  3. What We Will Get
  4. Background 
  5. The Answers Behind the "Why" Question 
  6. Using the Code 
  7. Points of Interest
  8. History 

Introduction 

Developing application for iPhone is a promising advantage for mobile application developers, especially if you are interested in objective-C programming. There are so many possibilities for developing apps in iPhone, and publish it to the Apple Appstore. Inspired by the iPad's potential app for read books, which is called "iBooks", why don't we explore and learn together about iPhone development by creating a simple reader app for iPhone.

In this tutorial, we will learn how to create a nice UIWebView based application, which will make us easier to manage the interface, e.g to place text and image. And of course, the user experience... This article will show you some useful tips on making our reading experience becomes much more similar to reading real book, such as turning the page by swiping, adding "sound" in the turning page progress, shake left and right capability to turn the page, universal magnifier glass etc.

In the end of this, you will get a "simple engine reader" plus some useful basic stuff on developing app on iPhone.

What We Will Get  

This is a list of features that we will implement from this tutorial:

  • UIWebView with capability of modified gesture behavior
  • Call iPhone provided controls using Java script
  • Play short sound using AudioServicesPlaySystemSound
  • Using Accelerometer to determine the shake left & right condition
  • Universal magnifier ready to be attached in any UIView

Background

Having basic knowledge of Objective-C is necessary to make it easier to understand this tutorial. I will try to explain everything as clearly as possible. However, the best learning resources come within the XCode, which is located in sub-menu Help->Documentation.

Another source that might be interested to be read is the sample code provided by Apple. For this tutorial, to make it easier to understand, you can see sample code about:


I also had posted a common error, which is usually done by iPhone app programmer, and a 'quick-on-the-spot' tools to detect it, you can read it on my blog here

The Answers Behind the "Why" Question 

I emphasize UIWebView on this tutorial because iPhone provides the UIWebView to display rich text and image format, which is useful for this simple reader, and it will become our main view for this tutorial. However, there is some compensation on using UIWebView. The gestures for interacting with another provided control is limited. For example, In normal way, you can't touch object attached on the UIWebView to raise an UIAlertView. There's a trick on this stuff, which I will explain too later in this tutorial.

This trick also include the usage of Javascript to be able to interact with provided control such as UIAlertView, or UIView animation.

The interface is simple, so In this tutorial I don't use the interface builder. It's a good practice for us too to code the layout. I create this tutorial using XCode Version 3.1.3. 

Using the code 

This is our overall strategy on building the simple reader:

  1. Construct a UIWebView for our simple reader
  2. Use the Javascript to interact with the other provided control (in this phase, we will add a javascript function to make an image attached on UIWebView can be used to raise an UIAlertView)
  3. Tweak it so it can receive gesture (gesture other than scroll, we will add the swipe capability to turn page with animation). 
  4. Add sound to the page transition animation
  5. Add Accelerometer to determine the shake condition
  6. Add Universal magnifier


Let's start the tutorial step by step:

1. Construct The Page for UIWebView (Our Main Interface)  

1. create new "view based application" project, name it as you wish, but I use "Reader" as the project's name


2. Import the assets used in this tutorial: (drag the file into the Resources folder (On the Group & Files Section), check the "copy item to destination's folder" on the dialog appear"). The asset folder (reader asset, uinzip first) contains:  

  • background for UIWebView (wood_bg.jpg)
  • picture for each pages (monkey.jpg and dog.jpg)
  • sound of turned page (page-flip-1.wav)

3. Now we want to create a base class called "page". This is a base class which contains definition of each page we will create.


Add a new file in the "Classes folder" (right click the Classes folder, add-> new file -> Cocoa Touch Class with type Objective-C class, name it as page.m, check the also create "class.h" ). It will add new file to the folder. Edit the "page.h", add the following code.

#import <Foundation/Foundation.h>

@interface page : NSObject {
    NSString *title;        //title of the page
    NSString* imageName;        //name of the image
    NSString *description;        //a little text for description
    
}

@property(nonatomic,retain) NSString *title;
@property(nonatomic,retain) NSString *imageName;
@property(nonatomic,retain) NSString *description;

@end

Now, let's add some code to "page.m"

#import "page.h"


@implementation page
@synthesize title,imageName,description;

-(id)init{                    //method called for the initialization of this object
    if(self=[super init]){}
    return self;
}

-(void)dealloc{                //method called when object is released
    [title release];            //release each member of this object
    [imageName release];
    [description release];
    [super dealloc];
}

@end

Our base class is considered done. Now let's construct the mainView

4. Basically, what will be viewed on our main view is the HTML which is more or less a HTML page that look like this (this is the page one):

HTML
<html>
<head>
    <meta name=viewport content=width=320/>        
</head> 
<body>
    <center><h1>This is Page One</h1></center>
    <center><IMG SRC="file://pathToImageFile" ALT ="imageName">
       </center><br><br>
    <center>This is a monkey</center>   
</body>
</html>

Simple, isn't it? So, let's make the mainView file. Add new file called "mainView" (like what we did before). Edit the "mainView.h" just like the following:

#import <Foundation/Foundation.h>


@interface mainView : UIWebView {    //change it so our main view inherits from UIWebView
    NSMutableArray *arrayPages;     //array of pages    
}

@property(nonatomic,retain)NSMutableArray *arrayPages;

-(void)produceHTMLForPage:(NSInteger)pageNumber;            //method for generating the HTML for appropriate page
-(NSString*)produceImage:(NSString*)imageName withType:(NSString*)imageType;    //method for attaching image to the HTML


@end

And the implementation file "mainView.m", which is the implementation:

#import "mainView.h"
#import "page.h"


@implementation mainView
@synthesize arrayPages;

-(id)initWithFrame:(CGRect)frame{    //initialization of this object will call this method
    if(self=[super initWithFrame:frame]){
        UIImageView *background = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"wood_bg.jpg"]]; //init a background
        self.backgroundColor=[UIColor clearColor];
        [self addSubview: background];            //add the background to our mainview
        [self sendSubviewToBack:background];            //move the background view to the back of UIWebView
        [background release];                //don't forget to release what's been allocated
        
        
        //init the arrayPages
        arrayPages =[[NSMutableArray alloc]initWithCapacity:5];
        //we hard code the page the page, but you can fill the content from anywhere later, e.g the XML web service
        page *pageone =[[page alloc]init];
        pageone.title=@" This is Page One";
        pageone.imageName=@"monkey";
        pageone.description=@"This is a monkey";
        [arrayPages addObject:pageone];
        [pageone release];
        
        page *pagetwo =[[page alloc]init];
        pagetwo.title=@"This is Page Two";
        pagetwo.imageName=@"dog";
        pagetwo.description=@"This is a dog";
        [arrayPages addObject:pagetwo];
        [pagetwo release];
        
    }
        return self;
}

-(void)produceHTMLForPage:(NSInteger)pageNumber{
    
    //init a mutable string, initial capacity is not a problem, it is flexible
    NSMutableString* string =[[NSMutableString alloc]initWithCapacity:10];    
    [string appendString:
    @"<html>"
        "<head>"
        "<meta name=\"viewport\" content=\"width=320\"/>"
        "</head>"
        "<body>"
     ];
        
    [string appendString:[NSString stringWithFormat:@"<center><h1>%@</h1></center>",[[arrayPages objectAtIndex:pageNumber-1]title]]];  
//we minus the index, since the initial index start at 0
    [string appendString:@"<center>"];
    [string appendString:[self produceImage:[[arrayPages objectAtIndex:pageNumber-1]imageName] withType:@"jpg"]];
    [string appendString:@"</center>"];
    [string appendString:[NSString stringWithFormat:@"<br><br><center>%@<center>",[[arrayPages objectAtIndex:pageNumber-1]description]]];
    [string appendString:@"</body>"
     "</html>"
     ];        //creating the HTMLString
    [self loadHTMLString:string baseURL:nil];        //load the HTML String on UIWebView
    [string release];
}

-(NSString*)produceImage:(NSString*)imageName withType:(NSString*)imageType{
    NSMutableString *returnString=[[[NSMutableString alloc]initWithCapacity:100]autorelease];
    NSString *filePath = [[NSBundle mainBundle]pathForResource:imageName ofType:imageType];
    if(filePath){
        [returnString appendString:@"<IMG SRC=\"file://"];
        [returnString appendString:filePath];
        [returnString appendString:@"\" ALT=\""];
        [returnString appendString:imageName];
        [returnString appendString:@"\">"];
        return returnString;
    }
    else
        return @"";
}


-(void)drawRect:(CGRect)rect{        //method that's called to draw the view
    [self produceHTMLForPage:1];
} 

-(void)dealloc{            
    [arrayPages release];
    [super dealloc];
}



@end

In our readerViewController.h, add the mainView to this particular controller

@class mainView;

@interface ReaderViewController : UIViewController {
    mainView* _mainView;
}

@property (nonatomic,retain)mainView* _mainView;

@end

And in the readerViewController.m, in the loadView method, set the view to the mainView.

 - (void)loadView {
    _mainView = [[mainView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];        //initialize a mainView
    self.view=_mainView;    //make the mainView as the view of this controller
    [_mainView release];    //don't forget to release what you've been allocated
    
}

Run your simulator, You will get something similar like this:

monkey.jpg

This is our first page! congratulations! In this page, you can scroll up and down the page, you can see the background, which is a wood-like surface, it makes the user feel comfortable and make them feel like reading the real book. But it's not finished yet, you still can't it to turn the page, right? Reading a book needs an experience, let's add it now!

2. Javascript for calling iPhone Control 

The iPhone only provides a method for executing javascript for the UIWebView, here is the method:

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script

However, this method only execute javascript within the UIWebView... How if we want to interact more using javascript? How if we want, let's say, calling UIAlertView when the user touch an image, How if we want to navigate to another view when the user touch some hyperlink? We need a trick on this, since there is no basic method on for conducting this condition. Let's work on it.

In this example, when the user touch the image, we will call the UIAlertView.

1. First, add the delegate for our "UIWebView", since we need to implement this method:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

Add the delegate:

@interface mainView : UIWebView <UIWebViewDelegate>

Don't forget to set the delegate in mainView initWithFrame method:

self.delegate=self;

2. Now, let's add script to our HTMLString, the function is called imageClicked, so basically we will add script between the head tag, just like this:

<head>
    <meta name=viewport content=width=320/>        
    <script>
    function imageClicked(){
        var clicked=true;
        window.location="/click/"+clicked;
        }
    </script>
</head> 

Then, we need to call the imageClicked() function when the image is clicked/touched by the user, just like this:

<center><a href="javascript:void(0)" onMouseDown="imageClicked()"><IMG SRC="file://pathToImageFile" ALT ="imageName">
    </a></center>

3. Almost done, now, when the user touch the image, we will call the iPhone control, UIAlertView, so we need to add this method to our mainView.m

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if ( [request.mainDocumentURL.relativePath isEqualToString:@"/click/false"] ) {    
        NSLog( @"not clicked" );
        return false;
    }
    
    if ( [request.mainDocumentURL.relativePath isEqualToString:@"/click/true"] ) {        //the image is clicked, variable click is true
        NSLog( @"image clicked" );
        
        UIAlertView* alert=[[UIAlertView alloc]initWithTitle:@"JavaScript called" 
               message:@"You've called iPhone provided control from javascript!!" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
        [alert show];
        [alert release];
        return false;
    }
    
    return true;
}

4. Done! basically, we set a boolean variable in the javascript, so that when the function imageClicked is executed, it will change the variable into true. This variable is checked on the method above to detect whether it's changed to the preferred condition or not. When the condition is right, you can invoke any iPhone control, such as calling UIAlertView or conducting animation, anything you need.  

Now run your simulator and voila! the UIAlertView is appeared on the screen!

javascript.jpg

OK, next requirement is the need of swiping to turn the page, let's do it

3. Adding Gesture Capability to UIWebView 

Again, in a UIWebView, what you can do are scroll the page, or use pinch to zoom the page. The behavior is fix. What if you want another behavior, what if when the user swipe horizontally, you want to modify the behavior? Let's say, executing an animation? Well, with a little tweak, you can do it. In iPhone iOS 4.0, there is a new capability to detect basic gesture by using UIGestureRecognizer.<code><code> Thanks to this capability, now we don't have to add much more work on detecting the gesture. Basically, UIGestureRecognizer <code>is a class that can be used to detect and handle swipe, tap, long press, etc by just declaring and set the event handler. Here's how   

1. Add the following code to initWithFrame method in the mainView.m

//....code 
//Using the UIGestureRecognizer for handling basic touch

		
UISwipeGestureRecognizer *swipeLeft =[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeLeftHandler)];
swipeLeft.direction=UISwipeGestureRecognizerDirectionLeft;
[self addGestureRecognizer:swipeLeft];
[swipeLeft release];
		
UISwipeGestureRecognizer *swipeRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeRightHandler)];
swipeRight.direction=UISwipeGestureRecognizerDirectionRight;
[self addGestureRecognizer:swipeRight];
[swipeRight release];
		
panTouch = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panTouchHandler:)];
panTouch.delaysTouchesBegan=YES;
[panTouch release]; 

The code above describe the behavior and the selector that will be executed when the swipe gesture detected

2. Add the event handler described in the selector above

-(void)swipeLeftHandler{
	NSString *filePath=[[NSBundle mainBundle]pathForResource:@"page-flip-1" ofType:@"wav"];
	NSURL *aFileURL = [NSURL fileURLWithPath:filePath isDirectory:NO];
	OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)aFileURL,&audioSID);
	if(error==0)
	{
		[self play:nil];	//play the sound
	}
	[self produceHTMLForPage:2];
}

-(void)swipeRightHandler{
	NSString *filePath=[[NSBundle mainBundle]pathForResource:@"page-flip-1" ofType:@"wav"];
	NSURL *aFileURL = [NSURL fileURLWithPath:filePath isDirectory:NO];
	OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)aFileURL,&audioSID);
	if(error==0)
	{
		[self play:nil];	//play the sound
	}
	NSLog(@"caucau");
	[self produceHTMLForPage:1];	
}

 

That's it! no more pain on detecting the gesture! What we did in the code above is actually how to detect a swipe gesture. UIGestureRecognizer <code><code>really will save our time...

5. Voila! we did it! run the simulator, and swipe the page, then it will go to the preferred page. 

Now, let's add a finishing touch. Adding a sound of turned page.

4. Adding Short Sound 

It's the user experience element that we will add to our simple reader. let's do it

1. We must add an AudioToolBox.framework to our project. On our Frameworks folder, right click and choose add->existing framework, and find the particular framework.

2. In the mainView.h, add a new member of our class, which is audioSID

SystemSoundID audioSID;        

3. And a method called "play"

-(void)play:(NSTimer*)theTimer;    //method for playing the short sound 

4. This is the implementation of play method, which is located in mainView.m

-(void)play:(NSTimer*)theTimer{    //implementation of the play sound
    AudioServicesPlaySystemSound(audioSID);
}  

5. Now, we will play the short sound when the swipe gesture is conducted, so we add the following code in the "fireTappedWithTouch" method

if((fabs(startLocation.y-endLocation.y)<=Y_TOLERANCE)&&
           (fabs(startLocation.x-endLocation.x)>=X_TOLERANCE)){    //checking the minimal condition of swiping            
            //initializing the sound for turned image
            NSString *filePath=[[NSBundle mainBundle]pathForResource:@"page-flip-1" ofType:@"wav"];
            NSURL *aFileURL = [NSURL fileURLWithPath:filePath isDirectory:NO];
            OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)aFileURL,&audioSID);
            if(error==0)
            {
                [self play:nil];    //play the sound
            } 

... //code 

6. Let's run the simulator once again, and when you do the swipe gesture, you will hear a short sound of turned page.

5. Adding Accelerometer to Detect Shake Gesture 

shaker.jpg

OK, some of us may already know how to use accelerometer, know the value of each direction (X,Y, and Z). But how to differ a tilt or shake? This will need a real device to be tested instead of using the emulator. And the shake gesture will have average acceleration more than 1.5. Then, it is good to go.

1. Add the UIAccelerometer to our mainView.h, together with another variables.

//shaking capability
	BOOL shake;			        //determine the shake
	UIAccelerometer *accelerometer;	        //the accelerometer
	UIAccelerationValue totalG;		//value from accelerometer 

2. Initialize within the initWithFrame method, and don't forget to set the delegate

self.accelerometer = [UIAccelerometer sharedAccelerometer];
		self.accelerometer.updateInterval = .1;
		self.accelerometer.delegate = self;

 3.  Implement the accelerometer delegate method

- (void)accelerometer:(UIAccelerometer *)acel didAccelerate:(UIAcceleration *)aceler {
	
    if (fabsf(aceler.x) > 1.5)
    {
        shake = YES;
        [NSTimer scheduledTimerWithTimeInterval:.75 target:self selector:@selector(endShake) userInfo:nil repeats:NO];
        return;
    }
	
    if(shake)
    {
        totalG += aceler.x;
    }
}

- (void) endShake {
    shake = NO;
    if (totalG < 0) [self swipeLeftHandler];
    if(totalG > 0) [self swipeRightHandler];
    totalG = 0;
}

Basically, we check the value of the accelerometer's acceleration, and determine the direction based on the value. We check it with time interval .75, but you can adjust it to the number you feel right on your application.

And Shake Gesture is done! test it on real device, and see when you shake your device to left, it will go to the next page, and vice versa.

6. Adding Universal Magnifier for zooming capability 

magnifiedScreen.jpg

No doubt that iPhone has the basic ability to enable the magnifier when it comes to editing the text. It's useful and cool. Why don't we add it on our application? 

And our tutorial to make a simple reader is finished! Well, it's not as good as the iPad's iBooks app, but with some creativity, you can add more features, such as load the page from XML Web Service, or you can add search capability, etc.

As usual, you can download the source code here

Download Reader.zip - 1.07 MB.

And all the asset for this simple reader here

Download reader_asset.zip - 258.04 KB

I hope it will make the tutorial easier to be understand.

Points of Interest

Our simple reader app shows some unique capabilities, which until this tutorial is released, the SDK (3.1.3) doesn't provide us the method that sometime we need, but with some clever trick, we can solve the problem such as:

  1. Ability to call iPhone control, such as UIAlertView or UIActionSheet using the javascript
  2. Add some modified gesture behavior capability to the UIWebView.
  3. Add shake gesture capability using accelerometer
  4. Add universal magnifier that can be used in any UIView

 

History 

  • June/6/2010 -- article released 
  • July/1/2010 -- changing the gesture (swipe to turn page) detection. Now use UIGestureRecognizer. Add shake capability by using accelerometer. Add Universal Magnifier

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)