iPhone开发学习笔记
http://blog.csdn.net/huanglx1984/article/details/4325377
?
?? return 1;
}
?
确实跟C很像吧。但是也不其然,objective c是一个很好的面向对象的语言。跟C还是有挺多不同之处。
我们先实现一个简单的类。
// Sample.h
#import <Foundation/NSObject.h>
@interface Sample: NSObject {
?? int a;
}
- (void) print;
- (void) setA: (int)a;
@end
?
咱们一句一句来看。
#import <Foundation/NSObject.h>
在objective c中,所有的类都必须继承NSObject,这个概念很想Java里面的Object。
?
objective c声明类的格式如下:
@interface className: baseClassName {
?? member variables;
}
member functions
@end
?
大家可能注意到objective c中函数的声明挺有特点的,我们现在来解释:
objective c的函数声明的基本格式如下
-/+ (return type) function_name;
-/+ (return type) function_name : (parameter type) parameter;
-/+ (return type) function_name : (parameter type) parameter1 otherParameter: (parameter_type) parameter2;
1) -/+: 这个称做scope, 如果在函数前面是- ,那么理解为一般的函数;如果是+, 可以理解为c++中的static函数
2) 函数的参数声明: objective c和 c++, java都很不一样。
如果没有参数的话,在函数名后面,可以什么都不写;
如果只有一个参数,在 : 后面声明参数的类型和名称;
如果有多个参数的话,每个参数前面都要有一个 : , 然后接着是参数类型和参数名称。可是大家可能还是觉得很奇怪。比如上面这个例子中, otherParameter这个东西是干什么的呢?在objective c中,对于有多个参数的函数,我们可以理解为将函数的名称拆成了几个部分,每个部分都是对紧接着的参数的一个解释。比如在C++中:void initializeRectangle(int x1, int y1, int x2, int y2), 但是我们并不知道这些参数都是什么意思;但是在objective c中,我们可以这样声明:void initializeRectangeWithLeftUpX: (int)x1 LeftUpY: (int)y1 RightBottomX: (int)x2 RightBottomY:(int)y2;
怎么样?有感觉了吧。
?
下面来实现这个简单的Sample类
// sample.m
#import <stdio.h>
#import "Sample.h"
@implementation Sample
- (void) print {
??? printf( "%d/n", a );
}
- (void) setA : (int) aa {
?? a = aa;
}
@end
?
使用这个类
#import "Sample.h"
int main( int argc, char* argv[] ) {
??? Sample* smp = [[Sample alloc] init];
??? [smp setA:1];
??? [smp print];
??? [smp release];
}
在objective c中,每一个类的对象都是一个指针,这和Java差不多(每个类的对象都用new声明)。alloc类似于C中的malloc, init是NSObject中的方法,用于初始化这个对象。如果我们实现了自己的初始化方法,也可以调用自己的初始化方法。使用完毕后,需要调用release释放空间。
在iPhone开发中,需要特别注意内存的管理。今后还会仔细些这部分的内容。
?
今天先写这么多。未完待续:)
?
File -> New Project -> 在左侧模板中选择iPhone OS 中的 application(只有一个选项)。Apple提供了好几个开发模板,包括Navigation based application,Open GL,View based application,window based application等等。其中,view based application是今天我们要介绍的。以后我们还会慢慢介绍其他的。在这些模板中,最为通用的是window based application,它相当于是一个空模板,所有的iPhone程序都可以基于window based这个模板来开发。而且,所有其他提供的模板都是在window based的基础上建立的。
?
好了,我们选择view based application,然后给我们第一个程序命名为HelloWorld。我们先来看一看xcode的开发环境。左侧是workspace浏览,右上是文件列表,右下是编辑区域。我们选中任何一个文件后,代码就会在编辑区域显示出来。
在左侧的workspace中,在HelloWorld的工程目录下,有五个文件夹,分别是Classes,Other Sources,Resources,Frameworks,Products。下面我们一一介绍。
1)Classes:这是源代码存放的地方。
2)Other Resources:这里面有两个文件,其中main.m是整个程序的入口,我们在里面可以看到熟悉的int main( int argc, char* argv[] )。不过一般而言,我们不需要碰这个文件。HelloWorld_Prefix.pch是一个预编译的头文件,里面是一些预先准备的头文件,一般也不用碰这个文件。
3)Resources:顾名思义,这里放的都是资源文件。如果我们的程序需要任何的外部资源,比如多媒体文件,数据库文件,都要放在这个目录下面。每一个iPhone程序都只能访问自己的Resources,其他程序的资源对它来说都是不可见的。所以不要希望能够通过文件系统访问其他地方的资源。
xib,这是非常重要的一个文件类型,我们以后会经常用到。双击这个文件会启动Interface Builder。xib文件中包含了所有关于界面设计的东西。而Interface Builder就是一个可视化的设计界面的工具,类似于MFC或者JBuilder中界面设计的工具。它可以采用拖拽的方式构建界面,大大简化了程序员的编码。
在这个目录中,有两个xib文件。MainWindow是整个程序的核心骨,每个iPhone程序都必须有这个文件,后面我们再详细看。HelloWorldViewController是我们这个程序的唯一一个页面(单页面程序)。
info.plist中可以修改一些关于这个程序的属性。
4)Frameworks:这里面是一些会用到的库,类似于C++中的lib,dll或者Java中的jar文件。今后我们会在开发其他程序的时候添加新的Framework。
5)这就是存放编译好的文件的地方。现在显示是红色的,表示这个文件不存在。
?
好了。现在双击MainWindow.xib,打开Interface Builder。会出现四个窗口。我们一一介绍:
1)MainWindow:
这里有5个图标:
A)Files Owner是这个xib文件的拥有者,它负责在程序启动的时候,从硬盘装载xib资源文件;
B)First Responder表示当前正在响应用户的对象,比如,用户点击了一个按钮,First Responder就是这个按钮;如果用户正在输入文本,那么这个Text Field就是First Responder;
C)HelloWorld App Delegate 这里引入了一个新的概念:Delegate。iPhone程序开发中,经常用到delegate。我们可以这样理解delegate,苹果为了简化开发者的工作,隐藏了很多实现的细节,它不希望程序员有这个能力去干涉一些基本的东西;但是为了不失灵活性,又为开发者提供了一些接口;这些接口可以看作是苹果下放给程序员的有限的控制权力。不过,这些接口设计的非常好,完全覆盖了我们程序员所需要的东西。我们可以将delegate理解为C语言中的回调函数(callback function),它会在特定的时候被自动调用;但是如果你不实现回调函数(也就是说只有一个空函数体的话),那么就意味着程序员放弃了这个控制的权力。
在Classes中有一个HelloWorldAppDelegate.m,这个就是我们实现“回调函数”的地方。我们看一看这个文件中的内容,目前它只实现了一个方法,就是applicationDidFinishLaunching,这个方法在应用程序装载完资源后调用。这里只有两行代码:
[window addSubview: viewController.view];
[window makeKeyAndVisible];
意思就是在装载完所有资源后,在主窗口中添加一个页面,然后显示。这就是我们实施控制的地方。我们不需要去关心程序是如何装载资源的,只需要关注在资源装载完毕以后,需要显示什么东西。
D)Hello World View Controller 这里我们再引入一个概念:MVC。iPhone sdk的设计遵守MVC的原则。所谓的MVC就是Model-View-Controller,它将数据、控制和显示分开,使得每一个部分都相对独立。我们举一个例子,在浏览网页的时候,我们看见的内容本身就是Model,但是这些内容通过页面显示出来,这个显示的方法就是View。比如说,同样的内容,我既可以用table的形式展现出来,也可以用list的形式展现出来,具体用什么形式,其实和数据本身,也就是model无关,所以一个好的设计就需要把model和view分开。同样,control也是一个道理。比如,用户点击某个按钮,就会显示出所有的数据,那么这个控制的动作和数据本身(model)也没有关系,不管是什么数据,都必须显示出来;这个控制,同样,也和怎么显示(view)无关,它只是控制逻辑(显示数据)。
那么这里的Hello World View Controller就是负责和用户交互的,将用户的命令传递给view和model(如果有的话)。这里,我们的程序很简单,所以没有model。我们再回过头看这行代码:
[window addSubview: viewController.view];
这就清楚很多了吧:将Hello World View Controller所控制的view加入主窗口中。
E)Window 顾名思义,就是应用程序的主窗口。iPhone程序是一个单window多view的程序。也就是说,它只允许每一个程序拥有唯一的一个window。我们可以在window中加入多个页面。
?
2)这里是我们设置Controller属性的地方。可以暂时理解为将页面上的控件和真正的代码联系起来的地方。我们下面会看到如何联系。
?3)这是我们的控件库。
?4)这是我们的页面。注意到,它上面显示“Loaded From HelloWorldViewController.nib”。这个意思就是说,这个程序的View是从HelloWorldViewController.xib这个文件装载的,所以真正的页面设计应该在HelloWorldViewController中完成。
好了,既然是这样,那么我们打开HelloWorldViewController.xib。同样会有四个窗口。找到View窗口(如下),
然后从控件库中拖入Label和Round Rect Button。双击这个Button,命名为Click。然后双击Label,删掉已有的文字“Label”,调整它的大小。保存。最后的application是,用户点击Click,出现Hello World!
?
回到xCode,在HelloWorldViewController.h中添加:
?
#import <UIKit/UIKit.h>
@interface HelloWorldViewController : UIViewController {
IBOutlet UILabel * m_label;
}
@property (nonatomic, retain) IBOutlet UILabel * m_label;
- (IBAction) showMessage;
@end
?
?
然后在HelloWorldViewController.m中添加代码:
?
#import "HelloWorldViewController.h"
@implementation HelloWorldViewController
@synthesize m_label;
... ...
?
- (IBAction) showMessage {
?? ? m_label.text = @"Hello World!";
}
?
- (void)dealloc {
?? ?[m_label release];
? ? [super dealloc];
}
?
下面再介绍两个概念IBOutlet和IBAction。他们都是以IB开头,读者大概已经猜到和Interface Builder有关系。没错,下面我们来详细解释一下。
我们虽然用Interface Builder添加了一个label,但是程序并不知道如何将这个控件和代码联系起来。Interface Builder为我们提供了这种能力。关键词IBOutlet就是告诉Interface Builder,程序员希望能将这个变量和某个控件联系起来。
按住ctrl,然后鼠标左键选中HelloWorldViewController.xib中的File's Owner;
拖拽鼠标至我们刚刚放置到View上的Label的上面,然后松开鼠标;
这时会看见一个popup,选择m_label;
这样,我们就把控件和代码联系起来了。
同样,我们虽然实现了一个方法,但是并没有将触发控件的事件和这个方法联系起来。关键词IBAction就是告诉Interface Builder,我们希望能用这个方法来响应某个事件。在函数的功能上,IBAction可以看作是返回空(void)。
选中Click这个button,然后组合键cmd(有一个苹果标志的键)+2,在Events中选中Touch Up Inside;
然后拖拽鼠标至HelloWorldViewController.xib中的File's Owner的上方,释放鼠标;
选择showMessage;
这样我们就完成了界面控件的事件和代码的关联。
?
编译(cmd+B)然后执行(cmd+R)。我们就能看到如下的页面。
今天我们完成了第一个iPhone的程序,也介绍了很多新的内容。希望读者能得到一些小的启发。今后我会陆续的介绍更多关于iPhone的程序设计。我们还会反复强调和复习今天的内容。
?
上图分别是本工程和上一个工程的workspace的截图。可以看到,如果使用window-based模板,在Classes文件夹中少了HelloWorldViewController,在Resources文件夹中少了HelloWorldViewController.xib。这是因为view-based模板给我们提供了所需的view,而window-based模板则把这个任务留给了开发者。
我们再来看一看本工程的HelloWorldAppDelegate.h和.m文件,跟上一篇中的HelloWorldAppDelegate相比,少了
1)HelloWorldViewController* viewController的声明,
2)和添加view的语句:[window addSubview:viewController.view];
这也是我们需要做的工作。
?
下面我们来添加一个新的view。
选中Classes文件夹,右键,选择Add->New File->选择iPhone OS下的Cocoa Touch Classes,然后在右边的菜单中选择UIViewController subclass,取名为HelloWorldViewController。(注意:记得选上“Also create "HelloWorldViewController.h"”)这样,我们就添加了控制这个新view的类-HelloWorldViewController。
我们还需要创建一个.xib文件,通过这个xib文件,可以编辑view的UI。选中Resources文件夹,右键,选择Add->New File->选择iPhone OS下的User Interfaces,然后在右边的菜单中选择View XIB,然后取名为HelloWorldViewController。
这样,我们就完成了新view的添加。可以这么理解:.xib文件是这个view的表现层(用户所看到的),而.h和.m文件则是这个view的控制(负责处理用户交互的)。如果我们还记得上一篇提到过的MVC结构,那么.xib就是V,而.h和.m就对应这C。
下面我们来添加代码和控件。
首先,在HelloWorldAppDelegate.h中:
#import <UIKit/UIKit.h>
@class HelloWorldViewController;
@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> {
??? UIWindow *window;
??? HelloWorldViewController *viewController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) HelloWorldViewController *viewController;
@end
这里在HelloWorldAppDelegate中添加了一个新的成员,就是我们刚添加的view的controller。注意,我们还添加了一句声明:@property (nonatomic, retain) HelloWorldViewController *viewController;
?
Objective-C中的关键词@property允许我们设置变量的属性。它的具体含义是什么呢?下面来详细解释一下:
在C++中,我们通过.运算符来访问成员变量,如aClass.memberVar = 1; 但是在Objective-C中,我们必须实现setter和getter函数才能访问和修改成员变量(如void setMemberVar(int i)和int getMemberVar())。为了简化编码,Objective-C提供了关键词@property,它告诉编译器为我们生成getter和setter函数。在@property后面,紧跟着一些属性,编译器根据这些属性,为getter和setter生成不同的代码。
1)nonatomic: 在默认情况下,编译器生成getter和setter函数的时候,会添加一些其他代码(主要是考虑到多线程的程序)。这里,我们不需要这些额外的代码,关键词nonatomic就是告诉编译器不要添加这些代码。
2)retain: 在默认情况下,编译器以assign的方式生成setter。而retain则告诉编译器,在生成setter函数的时候,需要调用setter参数的retain方法。如:
???????? *ASSIGN 方式*????????????????????????????????????????????????????? *RETAIN 方式*
- (void) setString: (NSString*) str {? ????????????????????? - (void) setString: (NSString*) str {
??????????? theString = str;???????????????????????????????????????????????????? [str retain];
??????????? ... ...??????????????????????????????????????????????????????????????????? [theString release];
?????????????????????????????????????????????????????????????????????????????????????? theString = str; ... ...
} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? }
?
在HelloWorldAppDelegate.m中,添加:
@synthesize window;
@synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {???
??? HelloWorldViewController *ctrl =
????? [[HelloWorldViewController alloc] initWithNibName:@"HelloWorldViewController"
??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? bundle:[NSBundle mainBundle]];
??? self.viewController = ctrl;
??? [ctrl release];
??? [window addSubview:[self.viewController view]];
??? // Override point for customization after application launch
??? [window makeKeyAndVisible];
}
- (void)dealloc {
??? [viewController release];
??? [window release];
??? [super dealloc];
}
1)@synthesize是真正的生成setter和getter函数的命令。在头文件中出现的@property只是设置属性的命令。
2)程序启动后,会自动装载MainWindow.xib,但是它并不知道要装载HelloWorldViewController.xib。这是我们添加的资源文件,所以在程序装载完MainWindow.xib之后,需要添加代码,让程序来装载HelloWorldViewController.xib。UIViewController中有一个成员变量view,这里HelloWorldViewController继承了UIViewController,自然也有这个成员变量。成员变量view对应于用户看到的UI(这里对应的UI是什么?见下文)。所以,需要将HelloWorldViewController的view添加到window中。
[window addSubview:[self.viewController view]];
3)由于viewController的属性是retain,所以在dealloc中,需要调用release释放资源。
?
下面我们来编辑这个新view所对应的UI。双击HelloWorldViewController.xib,启动Interface Builder。从控件库中添加UILabel和UIButton。调整控件的大小和位置。保存。
?
选择File's Owner,然后组合键cmd+4,在Class Identity中输入HelloWorldViewController。这是告诉Interface Builder将这个资源与HelloWorldViewController这个类联系起来。这样,我们就能够通过Interface Builder将这个资源中的控件与HelloWorldViewController类中的代码联系起来。
?
在HelloWorldViewController.h中,添加:
@interface HelloWorldViewController : UIViewController {
??? IBOutlet UILabel * m_label;
}
@property (nonatomic, retain) IBOutlet UILabel * m_label;
- (IBAction) onClick;
@end
由于我们需要修改UILabel中的文字,所以需要在代码声明这个UILabel,然后利用Interface Builder将这个变量和控件联系起来。同时,我们还声明了函数onClick,用来处理按键事件。同样,我们也要用Interface Builder将这个函数和控件事件联系起来。
?
在HelloWorldViewController.m中,添加:
@implementation HelloWorldViewController
@synthesize m_label;
... ...
- (IBAction) onClick {
??? m_label.text = @"Hello World!";
}
- (void)dealloc {
??? [m_label release];
??? [super dealloc];
}
然后就是用Interface Builder将它们和控件联系起来:
选中File's Owner,按住ctrl,点击鼠标左键并拖拽鼠标至UILabel上方,释放左键,选择m_label。这样就将变量和控件联系起来了。
选中控件UIButton,组合键cmd+2,选择Touch Up Inside,将鼠标移至Touch Up Inside右边的圆圈上(变成×),然后拖拽鼠标至File's Owner,释放左键,选择onClick。这样就完成了鼠标事件和函数的连接。
最后一步:上面我们提到过,UIViewController有一个成员变量view,这个view有对应的UI。很显然这里所谓的对应的UI,就是刚才我们使用Interface Builder编辑过的控件View(我们之前用Interface Builder添加了UIButton和UILabel到控件View中;在HelloWorldViewController.xib中,我们看到View旁边是一个小箭头,点击展开,下面有UILabel和UIButton),现在我们需要将UIViewController这个类中的成员变量view和控件View联系起来。这样,在运行代码:[window addSubview:[self.viewController view]];的时候,程序才知道需要装载哪个view。
选中File's Owner,组合键cmd+2,在Outlets下面,可以看到view现在没有和任何控件联系起来。将鼠标移至view右边的圆圈上(变成×),然后按住左键并拖拽鼠标至HelloWorldViewController.xib中的View上方,然后释放鼠标。这样,我们就完成了连接。
(上文中出现了两个不同的view,一个是UIViewController的成员变量view,另一个是控件View)
编译,运行!我们就得到了和上一篇一样的Hello World!
?
6)选中新添加的View Controller,组合键cmd(苹果的功能键)+4,在Class Identity下方,将Class名称改为SwitchViewController。这样,我们就将这个新添加的控件和代码(SwitchViewController类)联系起来了。保存。
?
下面开始代码部分。
在FoodListAppDelegate.h中:
@class SwitchViewController;
@interface FoodListAppDelegate : NSObject <UIApplicationDelegate> {
??? UIWindow *window;
??? SwitchViewController *viewController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet SwitchViewController *viewController;
@end
设计多view程序时,一定要有一个父View。在多view转换的时候,实际上是把一个view从父view中删除,然后将另一个view加入父view的过程。这里的父view就是我们的SwitchViewController的view(什么?SwitchViewController中怎么会有view,参考前一篇blog :) )。
在FoodListAppDelegate.m中:
#import "FoodListAppDelegate.h"
#import "SwitchViewController.h"
@implementation FoodListAppDelegate
@synthesize window;
@synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {? ?
??? [window addSubview:viewController.view];
??? // Override point for customization after application launch
??? [window makeKeyAndVisible];
}
- (void)dealloc {
??? [viewController release];
??? [window release];
??? [super dealloc];
}
同样,参见之前的文章(注:在以后的blog中,我也会注意减少这样的代码的出现次数,以保证我们能将篇幅集中在重要的问题上,读者可以参考以前的文章来了解这些基本知识)。
在SwitchViewController.h中:
#import <UIKit/UIKit.h>
@class WelcomeController;
@class FoodListController;
@interface SwitchViewController : UIViewController {
??? WelcomeController *welcomeController;
??? FoodListController *foodlistController;
}
@property (nonatomic, retain) WelcomeController *welcomeController;
@property (nonatomic, retain) FoodListController *foodlistController;
- (void) switchView;
@end
由于这个类用于控制两个View之间的转换,所以它的两个成员变量就是这两个View的Controller。函数 - (void) switchView就是用于转换view的函数。
在SwitchViewController.m中:
#import "WelcomeController.h"
#import "FoodListController.h"
@implementation SwitchViewController
@synthesize welcomeController;
@synthesize foodlistController;
... ...
- (void)viewDidLoad {
??? WelcomeController *wc = [[WelcomeController alloc] initWithNibName:@"WelcomeView" bundle:nil];
??? self.welcomeController = wc;
??? [wc release];
? ?
??? [self.view insertSubview:self.welcomeController.view atIndex:0];
??? [super viewDidLoad];
}
- (void) switchView {
??? if( self.foodlistController == nil ) {
??????? FoodListController *fc = [[FoodListController alloc] initWithNibName:@"FoodListView" bundle:nil];
??????? self.foodlistController = fc;
??????? [fc release];
??? }
??? if( self.welcomeController.view.superview != nil ) {
??????? [welcomeController.view removeFromSuperview];
??????? [self.view insertSubview:foodlistController.view atIndex:0];
??? }
??? else {
??????? [foodlistController.view removeFromSuperview];
??????? [self.view insertSubview:welcomeController.view atIndex:0];
??? }
}
... ...
- (void)dealloc {
??? [welcomeController release];
??? [foodlistController release];
??? [super dealloc];
}
下面我们来仔细看看这些代码:
1)首先来看看viewDidLoad,这个函数是在SwitchViewController的资源装载完毕后被自动调用的,在这个函数里面我们干了两件事情:
第一,从资源文件中装载了WelcomeView.xib,将它赋值给welcomeController;
第二,将welcomeController的view添加到switchViewController的view中;
我们在FoodListAppDelegate.m中,将viewController的view添加到主窗口中,所以viewController的 view就是我们用于转换不同view的地方。本质上,所谓的转换view,就是将一个view从viewController.view中删除,然后添加入另外一个view。由于我们想显示的第一个view一定是welcome的页面,所以这里就将welcomeController的view添加了进来。
我们在这里并没有装载FoodListView的资源,这是因为用户有可能看完第一个页面以后就退出了程序,所以没有必要在这个时候装载资源,iPhone开发中,把这种策略称为lazy loading。
2)switchView,这是我们实现转换view功能的地方。首先判断是否已经装载了FoodListView,如果还没有的话,就先装载这个资源。然后我们要判断目前显示在页面上的是哪一个view。通过这个语句就可以判断:
if (self.welcomeController.view.superview != nil ) {
}
它的含义是:welcomeController的view是否还有上一级的view呢?因为我们知道,任何一个view都要添加到 viewController的view中才能够显示出来,所以如果这个view目前显示在页面上,那么viewController的view就一定是它的上一级的view;如果这个view目前没有显示在也面上,就没有上一级的view。
转换view分两步:
1)将当前view从上一级的view中剔除: [welcomeController.view removeFromSuperview];
2)添加入新的view:[self.view insertSubview:foodlistController.view atIndex:0];
好了,到这里我们就完成了view转换的功能,下面我们来简单实现一下WelcomeController和FoodListController中的内容。
在WelcomeView.xib中添加一个Button,取名为Go to food list,然后在WelcomeController.h中添加一个方法
- (IBAction) switchToFoodList;
在WelcomeController.m中:
#import "WelcomeController.h"
#import "FoodListAppDelegate.h"
#import "SwitchViewController.h"
@implementation WelcomeController
... ...
- (IBAction) switchToFoodList {
??? FoodListAppDelegate *application = (FoodListAppDelegate*)[[UIApplication sharedApplication] delegate];
??? [application.viewController switchView];
}
这里来解释一下:我们想要调用SwitchViewController中的方法switchView,所以必须包含头文件SwitchViewController.h,而viewController这个对象又是FoodListAppDelegate的成员变量,所以我们要包含FoodListAppDelegate.h。我们通过:
[[UIApplication sharedApplication] delegate] 来得到FoodListAppDelegate类。
delegate 是UIApplication的成员变量,它是一个协议类(UIApplicationDelegate)。由于FoodListAppDelegate 遵守了UIApplicationDelegate协议,所以可以通过类型转换得到FoodListAppDelegate。这样我们就能调用其中的 viewController的 switchView方法了。
在FoodListView.xib中添加一个UILabel和一个Button,取名为Go back。然后在FoodListController.h中添加:
@interface FoodListController : UIViewController {
??? IBOutlet UILabel * label;
}
@property (nonatomic, retain) IBOutlet UILabel * label;
- (IBAction) switchToWelcome;
因为我们打算动态的向label中添加一些内容,所以这里我们要得到UILabel的对象。
在FoodListController.m中添加:
#import "FoodListController.h"
#import "FoodListAppDelegate.h"
#import "SwitchViewController.h"
@implementation FoodListController
@synthesize label;
- (void)viewDidLoad {??
???? NSString * list = [[NSString alloc] initWithFormat: @"beef/npork/nfish/ntomato/npotato/nsalad/n"];
??? label.text = list;
??? [list release];
??? [super viewDidLoad];
}
- (IBAction) switchToWelcome {
??? FoodListAppDelegate *application = (FoodListAppDelegate*)[[UIApplication sharedApplication] delegate];
??? [application.viewController switchView];
}
- (void)dealloc {
??? [label release];
??? [super dealloc];
}
这样我们就完成了所有的工作么?呵呵,还没有。不要忘了,我们还需要使用Interface Builder把控件和代码联系起来。
首先,打开MainWindow.xib,按住ctrl,选中Food List App Delegate,拖动鼠标至Switch View Controller上面,然后松开,选择viewController。这样,我们就把变量viewController和Switch View Controller这个资源联系起来了。
然后,打开WelcomeView.xib,将UIButton的Touch Inside Up事件和函数switchToFoodList联系起来。
最后,打开FoodListView.xib,将UILabel和变量label联系起来,将UIButton的Touch Inside Up事件和函数switchToWelcome联系起来。
好了,编译运行吧:) 你应该能看到:
这就是我们今天的内容。下面我还会针对这个简单的application做一些扩展,包括在view转换的时候加入一些动画效果,使用Table来现实我们的food list等等。
?
这是如何实现的呢?
首先,让我们花点时间回顾一下上一篇中的一个函数
- (void) switchView {
??? ... ...
??? if( self.welcomeController.view.superview != nil ) {
??? ??? [welcomeController.view removeFromSuperview];
??? ??? [self.view insertSubview:foodlistController.view atIndex:0];
??? }
??? else {
??? ??? [foodlistController.view removeFromSuperview];
??? ??? [self.view insertSubview:welcomeController.view atIndex:0];
??? }
}
这是实现View转换的地方。我们也是在这里加入动画的效果。
直接上代码:)
- (void) switchView {
?? ... ...
?? [UIView beginAnimations: @"Animation" context:nil];
?? [UIView setAnimationDuration: 1.25];
?? [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
?? if( self.welcomeController.view.superview != nil) {
????? [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES];
????? [foodlistController viewWillAppear:YES];
????? [welcomeController viewWillDisappear:YES];
????? [welcomeController.view removeFromSuperview];
????? [self.view insertSubview:foodlistController.view atIndex:0];
????? [welcomeController viewDidDisappear:YES];
????? [foodlistController viewDidAppear:YES];
?? }
?? else {
????? [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
????? [welcomeController viewWillAppear:YES];
????? [foodlistController viewWillDisappear:YES];
????? [foodlistController.view removeFromSuperview];
????? [self.view insertSubview:welcomeController.view atIndex:0];
????? [foodlistController viewDidDisappear:YES];
????? [welcomeController viewDidAppear:YES];
?? }
?? [UIView commitAnimations];
}
大家先看一下代码的第一行和最后一行,它们分别是beginAnimations和commitAnimations。这两句代码之间就是我们定义的animation block。所有关于动画的定义和设置都必须在animation block中完成。
下面是几条设置语句:
1)setAnimationDuration:这里设置了整个动画过程持续的时间,单位是秒
2)setAnimationCurve:这是设置了整个动画的速度。默认的是匀速动画。这里我们将它设置为EaseInOut,意思是在开始和结束的时候动画速度较慢,在中间过程动画速度较快,这样的动画显得更加的平滑。
3)setAnimationTransition:这里是设置动画的样式,iPhone提供了4种不同的样式,分别是:UIViewAnimationTransitionFlipFromLeft, UIViewAnimationTransitionFlipFromRight, UIViewAnimationTransitionCurlUp, UIViewAnimationTransitionCurlDown(读者可以自己一一尝试)。注意到还使用了cache。一般情况下我们都将此参数设为YES,用来加速动画的渲染。
?
在设置完成后,我们还加了几条语句,如:
[welcomeController viewWillAppear:YES];
[foodlistController viewWillDisappear:YES];
... ...
[foodlistController viewDidDisappear:YES];
[welcomeController viewDidAppear:YES];
怎么理解这些语句的作用呢?我们回想一下之前提过的delegate。Apple将很多实现的细节隐藏起来,程序员不需要干涉其中的部分;但是Apple又同时为程序员提供了足够的接口以满足我们特定的需要。这里的这四个函数就可以看作是这个功效:我们不需要去干涉具体的view转换的工作,我们只需要去控制4点:1)在这个view即将消失之前,我们需要做什么(viewWillDisappear);2)在这个view即将出现之前,需要做什么(viewWillAppear);3)在这个view已经消失之后,需要做什么(viewDidDisappear);4)在这个view已经出现之后,需要做什么(viewDidAppear)。这四个函数给了我们程序员足够的能力完成我们特定的需要。
举个例子,如果当前的view正在进行某个动画,当它即将消失的时候,我们希望能停止这个动画(因为已经对用户不可见了)。只需要在viewWillDisappear中实现这个功能就可以了。
这样就好了么?还没。。。为了完成这个动画,我们需要加入一个新的Framework。这里的Framework类似于C++中的dll或者lib,Java中的Jar文件。我们看一眼目前Frameworks这个文件夹中的内容,这些都是在新建工程时默认添加的库。选中Framework文件夹,右键,add to Project,在Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.1.sdk/System/Library/Frameworks中找到CoreGraphics.framework,点击Add,将其添加入工程。添加时注意:
1)不要check "Copy items into destination group's folder (if needed)
2)在Reference Type: 中选择Relative to Current SDK
注:今后如果还需要添加其他的Framework,也必须遵循以上的设置。
?
好了!编译并运行吧!
?
下面来说另外一种添加动画的方式。先来看代码:
?
#import <QuartzCore/CAAnimation.h>
#import <QuartzCore/CAMediaTimingFunction.h>
... ...
?
if( self.welcomeController.view.superview != nil ) {
??? ??? [self.welcomeController.view removeFromSuperview];
??? ??? CATransition *animation = [CATransition animation];
??? ??? [animation setDuration:0.5f];
??? ??? [animation setType:kCATransitionPush];
??? ??? [animation setSubtype:kCATransitionFromRight];
??? ??? [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
??? ??? [[self.view layer] addAnimation:animation forKey:@"switchView"];
??? ??? [self.view insertSubview:self.foodlistController.view atIndex:0];
}
简单来看看这个函数里的内容,CATransition这个类是用来实现layer transition的动画效果的。我们需要预先设置动画的时间(setDuration),需要的动画类型(setType和setSubtype)。Apple为我们提供了4种主类型(setType)和4种子类型(setSubtype)[参考:http://developer.apple.com/documentation/GraphicsImaging/Reference/CATransition_class/Introduction/Introduction.html]。最后,我们还需要设置动画的样式(setTimingFunction),和前面讲到的setAnimationCurve是一个概念。这里我们采用的是EaseInEaseOut,同UIViewAnimationCurveEaseInOut是一个意思。
?
注意,如果想使用CATransition,需要包含QuartzCore.framework,方法如前所述。
?
好了,言归正传,我们来看看如何在FoodList这个程序中使用TableView来显示所有的食物列表。
?
首先,我们来复习一下Protocol。所谓的Protocol就是接口,类似于Java中的Interface或者C++中的纯虚类。如果我们自己定义的类声明遵循某个Protocol的话,那么就意味着要去实现这个Protocol中的方法。比如,在Objective-C中,常常看到这样的类声明:
@interface class1 : baseClass <Protocol1, Protocol2> {
}
这就是说,Class1声明遵循两个Protocol,所以在它的实现中,就必须实现Protocol1和Protocol2中的相应的方法。
?
其次,再来复习一下delegate的概念。在“我们的第一个iPhone程序”中,我曾经提到过delegate的概念。下面再来简要的介绍一下:假如我们有两个类,A和B。B声明自己是A的delegate(代表),那么就意味着B要实现一些A中的方法,使得A在工作的时候,当它(A)需要调用某些方法的时候,可以去询问它的代言人─B。
这种模式在iPhone程序设计中非常的常见。继续用上面的例子做比方:往往A类都是iPhone提供的SDK,它隐藏了很多的实现细节,可是为了给程序员提供足够的灵活性,A又提供了一些接口方法,程序员通过实现这些接口方法,可以控制A类的某些行为。而实现这些接口方法往往都是通过delegate来完成的。一般来说,都是声明一个类B,使它成为A的delegate,然后在B中实现某些接口方法。这样,在A工作的时候,它就会去调用B中的对应的函数,来完成程序员指定的任务。
我们再看一个具体的例子:- (void)applicationDidFinishLaunching:(UIApplication *)application {}
上面这个函数是UIApplicationDelegate中的方法。每当我们创建一个自己的工程的时候,都会在ProjectNameAppDelegate(注:ProjectName就是我们给工程起的名字)这个类里面看到这个方法。实际上,ProjectNameAppDelegate就是UIApplication的一个delegate;当UIApplication完成装载资源的任务后,它就会去调用它的delegate─ProjectNameAppDelegate─中的这个函数。
好了,在熟悉完上面的两个概念之后,我们就可以开始编码了。我们在上次程序的基础上进行修改。这里,我们仅仅需要把原来用UILabel展示的数据变成用UITableView来展示,所以在代码方面需要做修改的,就仅仅是FoodListController这个类。
在FoodListController.h中:
#import <UIKit/UIKit.h>
@interface FoodListController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
// IBOutlet UILabel * label;
??? IBOutlet UITableView * table;
??? NSMutableArray * list;
}
//@property (nonatomic, retain) IBOutlet UILabel * label;
@property (nonatomic, retain) IBOutlet UITableView * table;
@property (nonatomic, retain) NSMutableArray * list;
- (IBAction) switchToWelcome;
@end
在这里,新声明了一个UITableView的对象和一个NSMutableArray对象。UITableView是要和Table View控件对应起来的,而NSMutableArray是存放被显示数据的地方(在这里就是存放我们的Food List)。
NSMutableArray可以理解为C++中的vector,可以动态的加入元素、删除元素等。
同时,我们将FoodListController这个类声明为遵循UITableViewDelegate和UITableViewDataSource这两个协议。其中,UITableViewDelegate是用来控制Table View的显示以及与用户之间的交互,而UITableViewDataSource则为Table View提供了后台的数据。
FoodListController这个类,就变成了UITableView的Delegate类。
?
在FoodListController.m中:
@implementation FoodListController
//@synthesize label;
@synthesize table;
@synthesize list;
... ...
- (void)viewDidLoad {
??? list = [[NSMutableArray alloc] init];
??? [list addObject:@"beef"];
??? [list addObject:@"pork"];
??? [list addObject:@"fish"];
??? [list addObject:@"tomato"];
??? [list addObject:@"salad"];
//? NSString * list = [[NSString alloc] initWithFormat:@"beef/npork/nfish/ntomato/npotato/nsalad/n"];
//? label.text = list;
//? [list release];
??? [super viewDidLoad];
}
... ...
- (void)dealloc {
// [label release];
??? [list release];
??? [super dealloc];
}
- (NSInteger) numberOfSectionsInTableView : (UITableView*)tableView {
??? return 1;
}
- (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section {
??? return [list count];
}
- (NSString*) tableView: (UITableView*)tableView titleForHeaderInSection: (NSInteger)section {
??? return @"Food List";
}
- (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath {
// NSUInteger section = [indexPath section];
??? NSUInteger row = [indexPath row];
???
??? static NSString * identifier = @"identifier";
??? UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];
??? if( cell == nil )
??? ??? cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier: identifier] autorelease];
??? cell.text = [list objectAtIndex:row];
??? return cell;
}
?
在viewDidLoad中,我们初始化了list的内容,用于Table的显示。
然后下面的4个方法:numberOfSectionsInTableView,tableView: numberOfRowsInSection,tableView: titleForHeaderInSection,tableView: cellForRowAtIndexPath,就是我们实现的delegate的方法。iPhone程序在现实Table View的时候,会在恰当的时候调用这几个方法。具体怎么调用我们不需要关系(这是被隐藏起来的实现细节),我们作为程序员只需要在delegate中实现这些方法,完成我们所需要的功能。下面一一来解释:
1)numberOfSectionsInTableView:这个方法的参数是UITableView*,也就是说,我们允许在一个View中有若干个Table View,可以为每个Table View分别设定section的数量。那么什么是section呢?参见下图。下图的Table View中一共有2个section。
这里,我们只需要一个section,所以函数直接返回1。
2)tableView: numberOfRowsInSection:这里有两个参数,第一个是UITableView*,第二个是section的index。也就是说它可以指定某个table view中的某个section的行数。这里,由于我们只有一个table view,并且在这个table view里面只有一个section,所以直接返回food list的长度。
3)tableView: titleForHeaderInSection:这里有两个参数,第一个是UITableView*,第二个是section的index。也就是说它可以指定某个table view中的某个section的标题。这里我们直接返回"Food List"。
4)tableView: cellForRowAtIndexPath:这个函数是用来返回Table View中每一行(每一个cell)的内容。它有两个参数,第一个是UITableView*,第二个是IndexPath*。IndexPath包含了该行所在的section的序号和它的行序号。我们可以通过[indexPath section]和[indexPath row]就可以得到该单元所在的section序号和行序号。
因为每一行唯一的区别就是显示的文本不同,所以为了节约资源,iPhone允许我们重用UITableViewCell的资源。
首先,使用[tableView dequeueReusableCellWithIdentifier: identifier]来查看一下UITableViewCell是否已经存在了。如果还没有存在,cell == nil,那么我们需要构造一个;如果已经存在了,那么我们需要做的就是根据它的行号,设置所需要显示的文本内容。
?
好了,到这里,我们就完成了编码的工作。下面我们双击FoodListView.xib,来修改这里的内容。原来,我们在这里放置了一个UILabel的控件,现在,我们将它替换成UITableView。如下图所示:
接下来,我们要将UITableView的delegate和data source都指定为File's Owner。如下图所示
这样,程序在运行时,就会知道要去FoodListController中寻找接口函数了。
最后,再将UITableView * table和控件联系起来。
好了!编译运行吧!你应该能看到:
?
怎么样,这个效果比之前我们看到的UILabel要好很多吧:)
之后,我们还会看到如何和UITableView进行一些交互,如何实现search的功能等等。
?
?
?
?
?
?
?
?
?
?
?