使用 UIWebView 构建简单的 iPhone 阅读器应用程序
通过开发简单的阅读器应用程序来学习 iPhone 上的应用程序开发基础知识。
- 下载 Reader.zip - 1.49 MB -- 源代码

目录
- 目录
- 引言
- 我们将获得什么
- 背景
- “为什么”问题的答案
- Using the Code
- 构建 UIWebView 的页面(我们的主界面)
- 用于调用 iPhone 控件的 Javascript
- 为 UIWebView 添加手势功能
- 添加短音效
- 添加加速度计以检测摇晃手势
- 添加通用放大镜以实现缩放功能
- 关注点
- 历史
引言
开发 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 创建了这个教程。
使用代码
这是我们构建简单阅读器的整体策略
- 构建一个 UIWebView 作为我们的简单阅读器
- 使用 Javascript 与其他提供的控件进行交互(在此阶段,我们将添加一个 Javascript 函数,使附加到 UIWebView 的图像能够触发 UIAlertView)
- 对其进行调整,使其可以接收手势(滚动以外的手势,我们将添加滑动能力以进行动画翻页)。
- 为页面转换动画添加声音
- 添加加速度计以确定摇晃条件
- 添加通用放大镜
让我们一步一步开始教程
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 }
运行您的模拟器,您将得到类似这样的结果
这是我们的第一页!恭喜!在此页面上,您可以上下滚动页面,可以看到背景,这是一个木质表面,让用户感到舒适,并让他们感觉像在阅读真实的书籍。但还没有完成,您仍然无法翻页,对吧?阅读书籍需要一种体验,让我们现在就添加它!
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 出现在屏幕上!
好的,下一个要求是需要滑动来翻页,我们来做吧
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. 添加加速度计以检测摇晃手势
好的,我们中的一些人可能已经知道如何使用加速度计,知道每个方向(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. 添加通用放大镜以实现缩放功能
毫无疑问,iPhone 在编辑文本时具有启用放大镜的基本能力。它很有用也很酷。为什么我们不将其添加到我们的应用程序中?
我们制作简单阅读器的教程就完成了!好吧,它不像 iPad 的 iBooks 应用那么好,但是通过一些创造力,您可以添加更多功能,例如从 XML Web 服务加载页面,或者添加搜索功能等。
像往常一样,您可以在这里下载源代码
以及这个简单阅读器的所有资源:
下载 reader_asset.zip - 258.04 KB
我希望它能让教程更容易理解。
关注点
我们的简单阅读器应用程序展示了一些独特的功能,直到本教程发布时,SDK (3.1.3) 并没有提供我们有时需要的方法,但是通过一些巧妙的技巧,我们可以解决问题,例如
- 使用 Javascript 调用 iPhone 控件(如 UIAlertView 或 UIActionSheet)的能力
- 为 UIWebView 添加一些修改后的手势行为能力。
- 使用加速度计添加摇晃手势能力
- 添加可用于任何 UIView 的通用放大镜
历史
- 2010 年 6 月 6 日 -- 文章发布
- 2010 年 7 月 1 日 -- 更改手势(滑动翻页)检测。现在使用 `UIGestureRecognizer`。使用加速度计添加摇晃能力。添加通用放大镜