65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 UIWebView 构建简单的 iPhone 阅读器应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (43投票s)

2010年6月6日

CPOL

10分钟阅读

viewsIcon

169448

downloadIcon

3688

通过开发简单的阅读器应用程序来学习 iPhone 上的应用程序开发基础知识。


目录 

  1. 目录 
  2. 引言 
  3. 我们将获得什么
  4. 背景 
  5. “为什么”问题的答案 
  6. Using the Code 
  7. 关注点
  8. 历史 

引言 

开发 iPhone 应用程序对移动应用开发者来说是一个有前景的优势,特别是如果您对 objective-C 编程感兴趣。在 iPhone 上开发应用程序的可能性非常多,并且可以发布到 Apple Appstore。受到 iPad 上名为“iBooks”的潜力阅读应用的启发,为什么我们不一起探索和学习 iPhone 开发,通过为 iPhone 创建一个简单的阅读器应用。

在本教程中,我们将学习如何创建一个漂亮的基于 UIWebView 的应用程序,这将使我们更容易管理界面,例如放置文本和图像。当然,还有用户体验……本文将向您展示一些有用的技巧,让我们的阅读体验更接近于阅读真实的书籍,例如通过滑动翻页、在翻页过程中添加“声音”、左右摇晃设备翻页的能力、通用放大镜等。

在教程结束时,您将获得一个“简单的引擎阅读器”以及一些在 iPhone 上开发应用程序的有用基础知识。

我们将获得什么 

这是我们将在本教程中实现的功能列表

  • 具有修改手势行为能力的 UIWebView
  • 使用 Javascript 调用 iPhone 提供的控件
  • 使用 AudioServicesPlaySystemSound 播放短音效
  • 使用加速度计确定左右摇晃条件
  • 通用放大镜,可附加到任何 UIView

背景

拥有 Objective-C 的基本知识对于更容易理解本教程是必要的。我会尽量清楚地解释一切。但是,最好的学习资源来自于 XCode,它位于子菜单“帮助”->“文档”中。

另一个可能感兴趣的来源是 Apple 提供的示例代码。对于本教程,为了便于理解,您可以在这里查看有关


我还发布了一个 iPhone 应用程序员通常会犯的常见错误,以及一个“即时”工具来检测它,您可以在我的博客上阅读:此处

“为什么”问题的答案 

我在此教程中重点介绍 UIWebView,因为 iPhone 提供了 UIWebView 来显示富文本和图像格式,这对于这个简单的阅读器很有用,并且它将成为我们本教程的主要视图。然而,使用 UIWebView 有一些折衷。与另一个提供的控件交互的手势是有限的。例如,正常情况下,您无法触摸附加到 UIWebView 的对象来触发 UIAlertView。对此有一个技巧,我将在本教程稍后解释。

这个技巧还包括使用 Javascript 与提供的控件(如 UIAlertView 或 UIView 动画)进行交互。

界面很简单,所以在这个教程中我没有使用界面构建器。这对我们来说也是一个很好的实践来编写布局代码。我使用 XCode 版本 3.1.3 创建了这个教程。

使用代码 

这是我们构建简单阅读器的整体策略

  1. 构建一个 UIWebView 作为我们的简单阅读器
  2. 使用 Javascript 与其他提供的控件进行交互(在此阶段,我们将添加一个 Javascript 函数,使附加到 UIWebView 的图像能够触发 UIAlertView)
  3. 对其进行调整,使其可以接收手势(滚动以外的手势,我们将添加滑动能力以进行动画翻页)。 
  4. 为页面转换动画添加声音
  5. 添加加速度计以确定摇晃条件
  6. 添加通用放大镜


让我们一步一步开始教程

1. 构建 UIWebView 的页面(我们的主界面) 

1. 创建一个新的“基于视图的应用程序”项目,名称随意,但我使用“Reader”作为项目名称


2. 导入本教程使用的资源:(将文件拖到“资源”文件夹(在“组和文件”部分),在出现的对话框中勾选“将项目复制到目标文件夹”)。资源文件夹(reader asset,请先解压)包含: 

  • UIWebView 的背景(wood_bg.jpg)
  • 每页的图片(monkey.jpg 和 dog.jpg)
  • 翻页的声音(page-flip-1.wav)

3. 现在我们要创建一个名为“page”的基类。这是一个基类,包含我们将创建的每个页面的定义。


在“Classes”文件夹中添加一个新文件(右键单击“Classes”文件夹,添加 -> 新文件 -> Cocoa Touch Class,类型为 Objective-C Class,命名为 page.m,同时勾选“也创建 'class.h'”)。这将在文件夹中添加一个新文件。编辑“page.h”,添加以下代码。

#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

现在,让我们在“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

我们的基类完成。现在让我们构建 mainView

4. 基本上,我们主视图上将显示的是 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>

很简单,不是吗?所以,让我们创建 mainView 文件。添加一个名为“mainView”的新文件(就像我们之前做的那样)。编辑“mainView.h”,使其如下所示

#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

以及实现文件“mainView.m”,这是实现

#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

在我们的 readerViewController.h 中,将 mainView 添加到这个特定的控制器

@class mainView;

@interface ReaderViewController : UIViewController {
    mainView* _mainView;
}

@property (nonatomic,retain)mainView* _mainView;

@end

在 readerViewController.m 中,在 loadView 方法中,将视图设置为 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
    
}

运行您的模拟器,您将得到类似这样的结果

monkey.jpg

这是我们的第一页!恭喜!在此页面上,您可以上下滚动页面,可以看到背景,这是一个木质表面,让用户感到舒适,并让他们感觉像在阅读真实的书籍。但还没有完成,您仍然无法翻页,对吧?阅读书籍需要一种体验,让我们现在就添加它!

2. 用于调用 iPhone 控件的 Javascript 

iPhone 只提供了一种执行 UIWebView 的 Javascript 的方法,这里是该方法

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

但是,此方法仅执行 UIWebView 中的 Javascript…如果我们想使用 Javascript 进行更多交互怎么办?如果我们想,比如说,当用户触摸图像时调用 UIAlertView 怎么办?如果我们想当用户触摸某个超链接时导航到另一个视图怎么办?我们需要一个技巧,因为没有基本方法来处理这种情况。让我们来处理它。

在此示例中,当用户触摸图像时,我们将调用 UIAlertView。

1. 首先,为我们的“UIWebView”添加代理,因为我们需要实现这个方法

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

添加代理

@interface mainView : UIWebView <UIWebViewDelegate>

别忘了在 mainView 的 initWithFrame 方法中设置代理

 self.delegate=self; 

2. 现在,让我们向我们的 HTMLString 添加脚本,该函数称为 imageClicked,所以我们基本上将在 head 标签之间添加脚本,如下所示

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

然后,我们需要在用户单击/触摸图像时调用 imageClicked() 函数,如下所示

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

3. 几乎完成了,现在,当用户触摸图像时,我们将调用 iPhone 控件 UIAlertView,所以我们需要将此方法添加到我们的 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. 完成!基本上,我们在 Javascript 中设置了一个布尔变量,这样当执行 imageClicked 函数时,它会将其更改为 true。在上面的方法中检查此变量以检测它是否已更改为首选条件。当条件正确时,您可以调用任何 iPhone 控件,例如调用 UIAlertView 或执行动画,您需要的一切。 

现在运行您的模拟器,瞧!UIAlertView 出现在屏幕上!

javascript.jpg

好的,下一个要求是需要滑动来翻页,我们来做吧

3. 为 UIWebView 添加手势功能 

再次,在 UIWebView 中,您可以滚动页面,或者使用捏合来缩放页面。行为是固定的。如果您想要另一种行为,如果您希望当用户水平滑动时,您想要修改行为怎么办?比如说,执行动画?好吧,稍微调整一下,您就可以做到。在 **iPhone iOS 4.0** 中,使用 `UIGestureRecognizer` 可以检测基本手势。多亏了这项功能,现在我们不必花费更多精力来检测手势。基本上,`UIGestureRecognizer` 是一个类,可以通过声明和设置事件处理程序来检测和处理滑动、点击、长按等。方法如下  

1. 在 mainView.m 的 **initWithFrame** 方法中添加以下代码**

//....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]; 

上面的代码描述了当检测到滑动手势时将执行的行为和选择器

2. 添加选择器中描述的事件处理程序

-(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];	
}

		

 

就是这样!检测手势不再痛苦!我们在上面的代码中所做的实际上是如何检测滑动手势。`UIGestureRecognizer` 确实为我们节省了时间…

5. 瞧!我们做到了!运行模拟器,然后滑动页面,它将转到首选页面。 

现在,让我们添加最后的润色。添加翻页的声音。

4. 添加短音效 

这是我们将添加到简单阅读器中的用户体验元素。我们来做吧

1. 我们必须将 AudioToolBox.framework 添加到我们的项目中。在我们的 Frameworks 文件夹中,右键单击并选择添加 -> 现有框架,然后找到相应的框架。

2. 在 mainView.h 中,为我们的类添加一个新的成员,即 audioSID

SystemSoundID audioSID;        

3. 以及一个名为“play”的方法

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

4. 这是位于 mainView.m 中的 play 方法的实现

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

5. 现在,我们将当滑动手势被执行时播放短音效,所以我们在“fireTappedWithTouch”方法中添加以下代码

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. 让我们再次运行模拟器,当您执行滑动手势时,您将听到短促的翻页声音。

5. 添加加速度计以检测摇晃手势 

shaker.jpg

好的,我们中的一些人可能已经知道如何使用加速度计,知道每个方向(X、Y 和 Z)的值。但是如何区分**倾斜**或**摇晃**?这需要一个真实的设备来测试而不是使用模拟器。而**摇晃**手势将具有**平均加速度大于 1.5**。然后,就可以开始使用了。

1. 将 `UIAccelerometer` 添加到我们的 mainView.h 中,以及其他变量。

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

2. 在 **initWithFrame** 方法中初始化,别忘了设置代理

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

 3.  实现加速度计委托方法

- (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;
}

基本上,我们检查加速度计的加速度值,并根据值确定方向。我们以 0.75 的时间间隔进行检查,但您可以根据您的应用程序感觉合适的值进行调整。

并且**摇晃手势**完成!在真实设备上测试,然后查看当您向左摇晃设备时,它将转到下一页,反之亦然。

6. 添加通用放大镜以实现缩放功能 

magnifiedScreen.jpg

毫无疑问,iPhone 在编辑文本时具有启用放大镜的基本能力。它很有用也很酷。为什么我们不将其添加到我们的应用程序中? 

我们制作简单阅读器的教程就完成了!好吧,它不像 iPad 的 iBooks 应用那么好,但是通过一些创造力,您可以添加更多功能,例如从 XML Web 服务加载页面,或者添加搜索功能等。

像往常一样,您可以在这里下载源代码

下载 Reader.zip - 1.07 MB。

以及这个简单阅读器的所有资源:

下载 reader_asset.zip - 258.04 KB

我希望它能让教程更容易理解。

关注点

我们的简单阅读器应用程序展示了一些独特的功能,直到本教程发布时,SDK (3.1.3) 并没有提供我们有时需要的方法,但是通过一些巧妙的技巧,我们可以解决问题,例如

  1. 使用 Javascript 调用 iPhone 控件(如 UIAlertView 或 UIActionSheet)的能力
  2. 为 UIWebView 添加一些修改后的手势行为能力。
  3. 使用加速度计添加摇晃手势能力
  4. 添加可用于任何 UIView 的通用放大镜

 

历史 

  • 2010 年 6 月 6 日 -- 文章发布 
  • 2010 年 7 月 1 日 -- 更改手势(滑动翻页)检测。现在使用 `UIGestureRecognizer`。使用加速度计添加摇晃能力。添加通用放大镜
© . All rights reserved.