当前位置: 代码迷 >> Iphone >> iPhone开发学习札记
  详细解决方案

iPhone开发学习札记

热度:111   发布时间:2016-04-25 06:41:08.0
iPhone开发学习笔记

http://blog.csdn.net/huanglx1984/article/details/4325377

?

Objective-C introduction

iphone使用objective c作为其开发语言(apple真是屌啊,就他们家用objective c吧。。),在学习iphone sdk之前,我们先看看objective c的基本特点吧。

?

objective c是一种c语言的变种,所以有时候在objective c中能看到写c语言的影子。

先列举几个浅显的objective c和c 的不同之处吧

1. objective c使用 nil 来表示 NULL

?

2. objective c用 YES NO 来表示 true false

?

3. objective c用 #import<stdio.h> 而不是#include<stdio.h>来包含头文件

?

4. 在objective c中,我们称类的方法(method)为消息(message)。在C++中,我们这样来调用方法: aClass->function(var); 但是在objective c中,我们使用 [aClass function:var]

objective c中的消息(message)比较有意思的特点是:

1) 调用消息的类可以不知道如何响应这个消息。如果它不知道如何处理这个消息,它会自动的将这个消息转给其他的类,比如它的父类;

2)调用消息的类可以是nil。在C++中,在使用类方法之前,我们都需要检查对象是否为空,所以在实现析构函数的时候,常会有如下的代码,如if (var) { delete var; } 但是在objective c中,我们就可以直接写[var release];? 即使var == nil, 也不会有问题。

?

照着老规矩,先写个hello world

#import <stdio.h>

int main( int argc, char* argv[] ) {

?? printf( "hello world/n" );

?? 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开发中,需要特别注意内存的管理。今后还会仔细些这部分的内容。

?

今天先写这么多。未完待续:)

?

Objective-C introduction - 2

上次说了Objective C是一种挺好的面向对象的语言。那么我们今天就来看看Objective C中的一些面向对象的特性吧。

?

构造函数 (constructor)

其实我觉得在Objective C中,这个名字并不算很恰当,可能叫做“初始化函数”比较合适一些吧。

因为这个函数其实就是一个普通的函数,和C++与Java中对构造函数的特殊待遇不同。

举个例子:


@interface Sample : NSObject {

??? int a;

}

- (Sample*) initWithIntValue: (int)aa;

- (void) setA : (int) aa;

- (void) print;

@end


@implementation Sample

- (Sample*) initWithIntValue: (int)aa {

???? if( self = [super init] ) {

???????? [self setA: aa];

???? }

???? return self;

}

// setA 和 print 的实现参见上一篇

@end

?

其实,initWithIntValue就是所谓的“构造函数”。我们在使用的时候,还是需要先调用父类NSObject中的alloc方法(注:alloc方法是一个static的方法,它是被“+”修饰的, 参见上篇关于函数声明的介绍):

Sample* smp = [[Sample alloc] initWithIntValue:1];

?

对构造函数的几个说明:

1) 返回值一定要是类的指针

2) 一定要先调用父类NSObject的init函数,因为在Objective C中所有的类都是从NSObject继承来的

3) 检查init返回的值是否有效

4) self: 这是Objective C的一个关键字,概念上和C++与Java中的this一样

在面向对象程序设计中,大家一定很熟悉访问限制的概念,也就是C++和Java中的public, protected, private,在Objective C中也有类似的东西

#import <Foundation/NSObject.h>
@interface AccessExample: NSObject {
@public
??? int publicVar;
@protected
??? int protectedVar;
@private
??? int privateVar;
}
@end

?

没错,就是挺简单的。

还记得之前说的Objective C中的静态方法么(static messages)?下面我们来看一个例子:

?

// ClassA.h

#import <Foundation/NSObject.h>
static int count;

@interface ClassA:NSObject
+ (int) getCount;
+(void) initialize;
@end

?

// Implementation
@implementation ClassA
- (id) init {
??? self = [super init];
??? count++;
??? return self;
}
+ (int) getCount {
??? return count;
}
+ (void) initialize {
??? count = 0;
}
@end

?

static int count;

在C++中,还记得怎么声明静态变量么?

class A {public:   A();   ~A();protected:   static int a;}static int A::a = 0;
?

但是在Objective C中,所谓类的静态变量其实可以理解为一个全局的静态变量,因为它并没有被放在@interface的定义里面。

接下来,getCountinitialize是两个静态方法。getCount用于返回当前对象的个数,而initialize用于清空对象的计数。

但是在Implementation中,为什么会有init这个方法呢?

是的,这里可以理解为,我们重载了NSObject中的init方法:仅增加了一个功能,就是计数。我们不需要在头文件中声明init,因为我们继承了NSObject。

?

如何使用这个类呢:

int main( int argc, char* argv[] ) {
??? [ClassA initialize];

??? ClassA *c1 = [[ClassA alloc] init];
??? ClassA *c2 = [[ClassA alloc] init];
???

??? printf( "ClassA count: %i/n", [ClassA getCount] );

? ? ... ...?
}

?

读者自己试一试实现自己的release方法吧:)

未完待续~~

?

Objective-C introduction - 3

我们接着来看objective c中面向对象的特性。要谈到面向对象,就不可能不说继承和多态。
其实,我们之前就已经谈到了继承,因为在objective c中,所有类都是从NSObject继承而来的。
继承,可以理解为“is-a”的关系,这个概念相信对大部分人来说都在熟悉不过了,关于C++和Java的任何一本书都会详细介绍这个概念,这里我不再赘述,直接上例子。

// Rectangle.h
#import <Foundation/NSObject.h>
@interface Rectangle:NSObject {
?? ?int width, height;
}
- (Rectangle*) initWithWidth:(int)w andHeight:(int)h;
- (void) setWidth: (int)w;
- (void) setHeight: (int)h;
- (void) setWidth: (int)w andHeight:(int)h;
- (int) width;
- (int) height;
- (void) print; ?// printf("width is %i, height is %i/n", [self width], [self height]);
@end


- (Rectangle*) initWithWidth:(int)w andHeight:(int)h- (void) setWidth: (int)w andHeight:(int)h?都是带多个参数的函数声明(参见第一篇)。

我们再实现一个正方形的类,继承矩形。
// Square.h
#import "Rectangle.h"
@interface Square:Rectangle {
}
- (Square*) initWithSize: (int)s;
- (void) setSize: (int)s;
- (int) size;
@end
// Square.m
@implementation Square
- (Square*) initWithSize: (int)s {
?? ?if( self = [super init] ) {
?? ? ? [self setSize:s];
?? }
?? return self;
}
- (void) setSize: (int)s {
?? ?width = s;
?? ?height = s;
}
- (int) size {
?? ?return width;
}
- (void) setWidth: (int)w {
?? ?[self setSize:w];
}
- (void) setHeight: (int)h {
?? ?[self setSize:h];
}
- (void) print {
?? printf( "the size is %i/n", [self size] );
}
@end

上面这个正方形类,继承了矩形类的成员变量。但是由于正方形的几何特殊性(长=宽),所以在正方形类中添加了三个方法。
分别是initWithSize,setSize,size。另外,我们重载了矩形类中的setWidthsetHeight方法,因为正方形不允许分别设置长和宽。
如何使用:
int main( int argc, char* argv[] ) {
?? ?Rectangle* rect = [[Rectangle alloc] initWithWidth:10 andHeight:5];
?? ?Square* sq = [[Square alloc] initWithSize:10];
?? ?[rect print];
?? ?[sq print];
?? ?[rect release];
?? ?[sq release];
}

有了继承,我们就来说一说多态性。C++中虚函数的概念相信大家都不陌生,它通过一个虚拟表(virtual table)实现了动态绑定(dynamic binding)。
在objective c中也有类似的概念。
我们就以上面两个类为例子:
int main ( int argc, char* argv[] ) {
?? ?Rectangle *rect = [[Rectangle alloc] initWithWidth:10 andHeight:5];
?? ?Square *sq = [[Square alloc] initWithSize: 5];
?? ?id shape;
?? ?
?? ?shape = rect;
?? ?[shape print]; ? // call the Rectangle's print

?? ?shape = sq;
?? ?[shape print]; ? // call the Square's print

?? ?... ...
}

这里,引入了objective c的一个很重要的关键词 id
我们可以将id理解为C++中的void*,所以我们能将rect和sq都赋值给shape。
那么shape是怎么知道调用哪个版本的print的呢?
还记得第一篇中我们提到的message的概念么?虽然id不知道如何响应print,但是它可以把这个消息传递给rect或者sq。

和c++与Java一样,objective c也支持run-time的类类型检查
- (BOOL) isKindOfClass: classObj
用于判断该对象是否属于某个类或者它的子类。

// true

如: if( [sq isKindOfClass: [Rectangle class]] == YES ) {}

- (BOOL) isMemberOfClass: classObj
用于判断该对象是否属于某个类(这里不包括子类)

// true

如: if( [sq isMemberOfClass: [Rectangle class]] == NO ) {}

- (BOOL) respondsToSelector: selector
用于判断该对象是否能响应某个消息。这里,我们可以将@selector后面带的参数理解为C++中的函数指针。
注意:1)不要忘了@ 2)@selector后面用的是(),而不是[]。3)要在消息名称后面跟:,无论这个消息是否带参数。

// true

如: if( [sq respondsToSelector: @selector(setSize:)] == YES ) {}

+ (BOOL) instancesRespondToSelector: selector
用于判断该类是否能响应某个消息。这是一个静态函数。

// true

如: if( [Square instancesRespondToSelector: @selector(setSize:)] == YES ) {}

?

Objective-C introduction - 4

我们之前说到Objective-C是一种很好的面向对象的语言,和C++Java相比,Objective-C有一些自己独特的东西,下面我们来简单的介绍一下。

?

1)Category

回想一下,在C++中,如果我们想继承一个类,给它添加一些新的功能,我们需要什么?当然是我们需要得到这个类的源代码。但是在Objective-C中,由于有了这个Category的概念,我们可以在没有源代码的情况下,为一个已经存在的类添加一些新的功能,比如:

?

// DisplayMath.h

@interface Rectangle(Math)

- (int) calculateArea;

- (int) calculatePerimeter;

@end


// DisplayMath.m

@implementation Rectangle(Math)

- (int) calculateArea {

??? return width*height;

}

- (int) calculatePerimeter {

??? return 2*(width+height)

}

@end

?

这里,使用了之前定义的Rectangle类,我们想为它添加两个功能:计算面积和周长。即使没有Rectangle类的源代码,我们也可以通过Category创建Rectangle的子类。

使用Category的时候,需要注意两个地方:

1) 只能添加新的方法,不能添加新的数据成员

2)Category的名字必须是唯一的,比如这里,就不允许有第二个Math存在。

?

如何使用:

int main( int argc, char* argv[] ) {

??? Rectangle *rect = [[Rectangle alloc] initWithWidth: 5 andHeight:10];

??? [rect calculateArea];

??? [rect release];

}

2)如何创建私有方法

我们知道可以使用@private来声明私有变量,但是如何声明私有方法呢?

Objective-C中并没有什么关键词来修饰方法,如果我们想让某个方法不被其他的类所见,唯一的方法就是不让这个方法出现在头文件中。比如:

// MyClass.h

#import <Foundation/NSObject.h>

@implementation MyClass

- (void) sayHello;

@end


// MyClass.m

#import "MyClass.h"

@implementation MyClass

- (void) sayHello {

??? NSLog(@"Hello");

}

@end


@interface MyClass(Private)

- (void) kissGoodbye;

@end

@implementation MyClass(Private)

- (void) kissGoodbye {

??? NSLog(@"kissgoodbye");

}

@end

?

怎么样,看到了Category的应用么?是的,利用Category可以方便的实现“私有”方法。

3)Protocol

Objective-C中,Protocol的概念很象Java中的interface或者C++中的virtual class

来看下面这个例子:

@protocol Printing

- (void) print;

@end


// MyDate.h

@interface MyDate: NSObject <Printing> {

??? int year, month, day;

}

- (void) setDateWithYear: (int)y andMonth: (int)m andDay: (int)d;

@end


// MyDate.m

#import <stdio.h>

#import "MyDate.h"

@implementation MyDate

- (void) setDateWithYear: (int)y andMonth: (int)m andDay: (int)d {

??? year = y;

??? month = m;

??? day = d;

}

- (void) print {

?? printf( "%4d-%2d-%2d", year, month, day );

}

@end

?

我们首先声明了Printing协议,任何遵守这个协议的类,都必须实现print方法。在Objective C中,我们通过<>来表示遵守某个协议。当某个类声明要遵守某个协议之后,它就必须在.m文件中实现这个协议中的所有方法。

如何使用:

int main( int argc, char* argv[] ) {

??? MyDate * dat = [[MyDate alloc] init];

??? [dat initDateWithYear:1998 andMonth:09 andDay:01];

???

??? id<Printing> var = dat;

??? [var print];


??? if( [dat conformsToProtocol:@protocol(Printing)] == YES ) {} // true


??? [dat release];

}

?

注意两个地方:1)使用id<Printing>作为类型,而不是象C++中,使用Printing* var; 2)conformsToProtocol类似于之前所说的respondsToSelector,用于动态检查某个对象是否遵守某个协议。

?

Objective-C introduction - 5

iPhone程序开发时内存的管理

?

在开发iPhone程序时,一定要特别小心内存的管理。其实基本的道理很简单,就像我们以前写C++程序一样,newdelete要成对出现。问题是在某些时候,我们没有意识到自己使用了new

?

Objective-C中对内存的管理采用引用计数的技术。简单说就是,当我们拥有一个变量的时候,这个变量的计数就加1,当我们释放这个变量的时候,这个变量的计数就减1。当计数为0时,这个变量就可以合法的被删除了。

?

1)alloc 很明显,这个函数调用之后,变量的计数加1。所以在调用alloc之后,一定要调用对应的release

2)retain 保留一个对象。调用之后,变量的计数加1。或许不是很明显,我们举个例子

- (void) setName : (NSString*) name {

???? [name retain];

???? [myname release];

???? myname = name;

}

?

我们来解释一下:设想,用户在调用这个函数的时候,他注意了内存的管理,所以他小心的写了如下代码:

NSString * newname = [[NSString alloc] initWithString: @"John"];

[aClass setName: newname];

[newname release];

?

我们来看一看newname的计数是怎么变化的。首先,它被alloc,count = 1; 然后,在setName中,它被retain, count = 2; 最后,用户自己释放newname,count = 1,myname指向了newname。这也解释了为什么需要调用[myname release]。我们需要在给myname赋新值的时候,释放掉以前老的变量。

3)copy 返回某个对象的一个拷贝。这个拷贝是一个新的对象,它的计数为1,需要在将来被release。

?

基本上,所有带有alloc, retain, copy的函数,都会使得变量的计数加1。因此在调用完这些方法之后,要小心release。另外还有一些初始化函数,它们没有带有alloc, retain或者copy的字样,比如stringWithFormat,它们并不会使变量计数加1。

Autorelease

在Objective-C中,这个概念很象Java中的Garbage Collection。它会把内存的管理交给另一个系统(autorelease pool)。在某些时候,我们在一个函数内部创建(alloc)了一个变量,但是我们无法在函数内部将这个变量释放(确实会有这种情况出现。。。),而显然,我们不可能在函数外部释放这个变量,这时候就可以借助Autorelease来帮忙了。据说,我们应该尽量自己管理内存,不要随便交给Autorelease。天下没有免费的午餐啊,呵呵。自己省了事,但是也失去了对内存的控制。。。天知道autorelease pool啥时候才会给你release啊。。。

Objective-C提供了一些容器,如NSArray, NSDictionary, NSSet等等,加入容器的对象都会使计数加1。这和C++中的容器类很相似。我们在C++中,常常看到:

void addElement( vector<ClassA>& my_list; )

{

??? ClassA object1;

??? my_list.push_back(object1);

}

虽然object1是一个临时变量,在函数调用结束后会自动销毁,我们仍然可以安全的添加元素。

所以在Objective-C中,我们也能看到如下代码:

NSMutableArray * array = [[NSMutableArray alloc] init];

NSNumber *num = [[NSNumber alloc] initWithInt: 1];

[array addObject: num ];

[num release];

... ...

再看两个编码上可以考量的地方:

1)为什么我们很少看见有人这样写代码:

AClass * class = [AClass alloc];

[class init];

而基本上都是 AClass * class = [[AClass alloc] init];

原因很简单,因为我们需要确保当init调用失败的时候,class是一个无效的值(nil)。如果使用第一种方式,我们无法通过class的值知道init是否调用成功。这也告诉我们,在实现自己的init方法时,需要在失败的时候返回nil

2)一般在dealloc方法中,我们都会释放一些变量,所以我们经常看到

- (void) dealloc {

??? [ var release];

??? [ super dealloc];

}

这样写当然没有错,但是我看见过某些“geek”分析到:

在调用release之后,虽然变量被释放了,可是它的值仍然是有效的。就好像我们在C++里面,delete一个变量之后,一个好的习惯是,接着将这个变量赋值为0。所以,最优的方法是加上var = nil;

?

以上就是一些使用Objective-C需要注意的地方。

?

到这里,我基本上说完了关于Objective-C的基础内容,当然,它本身还有很多东西,不可能通过短短几篇博客就说完。而且我使用Objective-C的时间也不长,无法做到面面俱到。但是,个人认为掌握这些基本概念之后,我们就可以开始iPhone程序的开发了,在开发的过程中继续加深对Objective-C的理解。

?

我们的第一个iPhone程序

万事开头难。

?

在大致了解了objective c的基本特性之后,今天我们来开发自己的第一个iPhone程序。希望能通过第一个iPhone程序的开发,达到以下几个目的:

1)了解xCodeinterface builder的使用。

2)大致了解iPhone程序的基本结构

3)在模拟器上运行这个程序

?

iPhone在任意时刻只能允许有一个application运行(所以不可能会有后台程序),而一个application可以有多个view(页面)。今天我们先来看看单页面(single-view)的程序。

?

我们先运行xCode


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 Builderxib文件中包含了所有关于界面设计的东西。而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 这里引入了一个新的概念:DelegateiPhone程序开发中,经常用到delegate。我们可以这样理解delegate,苹果为了简化开发者的工作,隐藏了很多实现的细节,它不希望程序员有这个能力去干涉一些基本的东西;但是为了不失灵活性,又为开发者提供了一些接口;这些接口可以看作是苹果下放给程序员的有限的控制权力。不过,这些接口设计的非常好,完全覆盖了我们程序员所需要的东西。我们可以将delegate理解为C语言中的回调函数(callback function),它会在特定的时候被自动调用;但是如果你不实现回调函数(也就是说只有一个空函数体的话),那么就意味着程序员放弃了这个控制的权力。

Classes中有一个HelloWorldAppDelegate.m,这个就是我们实现“回调函数”的地方。我们看一看这个文件中的内容,目前它只实现了一个方法,就是applicationDidFinishLaunching,这个方法在应用程序装载完资源后调用。这里只有两行代码:

[window addSubview: viewController.view];

[window makeKeyAndVisible];

意思就是在装载完所有资源后,在主窗口中添加一个页面,然后显示。这就是我们实施控制的地方。我们不需要去关心程序是如何装载资源的,只需要关注在资源装载完毕以后,需要显示什么东西。

D)Hello World View Controller 这里我们再引入一个概念:MVCiPhone sdk的设计遵守MVC的原则。所谓的MVC就是Model-View-Controller,它将数据、控制和显示分开,使得每一个部分都相对独立。我们举一个例子,在浏览网页的时候,我们看见的内容本身就是Model,但是这些内容通过页面显示出来,这个显示的方法就是View。比如说,同样的内容,我既可以用table的形式展现出来,也可以用list的形式展现出来,具体用什么形式,其实和数据本身,也就是model无关,所以一个好的设计就需要把modelview分开。同样,control也是一个道理。比如,用户点击某个按钮,就会显示出所有的数据,那么这个控制的动作和数据本身(model)也没有关系,不管是什么数据,都必须显示出来;这个控制,同样,也和怎么显示(view)无关,它只是控制逻辑(显示数据)。

那么这里的Hello World View Controller就是负责和用户交互的,将用户的命令传递给viewmodel(如果有的话)。这里,我们的程序很简单,所以没有model。我们再回过头看这行代码:

[window addSubview: viewController.view];

这就清楚很多了吧:将Hello World View Controller所控制的view加入主窗口中。

E)Window 顾名思义,就是应用程序的主窗口。iPhone程序是一个单windowview的程序。也就是说,它只允许每一个程序拥有唯一的一个window。我们可以在window中加入多个页面。

?

2)这里是我们设置Controller属性的地方。可以暂时理解为将页面上的控件和真正的代码联系起来的地方。我们下面会看到如何联系。



?3)这是我们的控件库。



?4)这是我们的页面。注意到,它上面显示“Loaded From HelloWorldViewController.nib”。这个意思就是说,这个程序的View是从HelloWorldViewController.xib这个文件装载的,所以真正的页面设计应该在HelloWorldViewController中完成。



好了,既然是这样,那么我们打开HelloWorldViewController.xib。同样会有四个窗口。找到View窗口(如下),


然后从控件库中拖入LabelRound 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];

}

?

下面再介绍两个概念IBOutletIBAction。他们都是以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的程序设计。我们还会反复强调和复习今天的内容。

?

我们的第一个iPhone程序-续

上次我们知道了如何build一个简单的iPhone应用程序。我们不着急往下走。

今天我们花一些时间,从另外几个方面探讨一下上次HelloWorld的程序。

这个HelloWorld可谓麻雀虽小,五脏俱全。

?

首先,让我们再来看看MainWindow.xib。好奇的读者可能上次就会有疑问,这个程序到底是如何运行起来的呢?

任何一个iPhone程序运行时,都会首先装载MainWindow.xib

?

上一篇详细解释了存在于MainWindow.xib之中的五个对象,在程序装载MainWindow.xib资源的时候,除了File OwnerFirst Responder之外,其余的几个组件都会有相应的类对象被创建。比如,HelloWorld App Delegate所对应的HelloWorldAppDelegate类,就会有一个对象被创建。我们在代码中实现了HelloWorldAppDelegateHelloWorldViewController类,这两个类的对象都会在初始化的时候被创建,分别对应着HelloWorld App DelegateHello World View Controller这两个组件。UIWindow这个类是Apple默认提供的,不需要我们实现,没有实现,并不代表没有。因此,UIWindow这个类的对象也被创建,对应于组件Window

?

那么各个组件之间是什么关系,iPhone程序是怎么知道它们之间关系的呢?首先,我们选中File OwnerFile Owner的类型是UIApplication,而每一个UIApplication都有一个相应的delegate,开发者可以通过实现delegate中的一些方法,完成对UIApplication的控制。按组合键cmd(苹果键)+2,可以看到delegate是和HelloWorld App Delegate联系在一起的。它的意思是:这个applicationdelegate是通过HelloWorld App Delegate来实现的。

?

我们在HelloWorldAppDelegate中定义了两个变量,一个是UIWindow,另一个是HelloWorldViewController,所以很明显,在这个程序的delegate被创建之时,WindowController也都被创建了。同样,选中HelloWorld App Delegate,按组合键cmd+2,可以看到viewControllerHello World View Controller联系在一起;而window则是和Window联系在一起,这样,我们就把HelloWorldAppDelegate与?UIWindow、HelloWorldViewController之间的关系确定了。

?

选中Hello World View Controller,然后按组合键cmd+1,在NIB Name一栏中有HelloWorldViewController,这说明在创建HelloWorldViewController的时候,会装载名为HelloWorldViewControllerxib资源。这样,另一个xib资源也被装载了。

?

HelloWorldViewController.xib被装载的过程和MainWindow.xib很类似。除了File OwnerFirst Responder之外,其余的组件都会有相应的类对象被创建。这里,首先是UIView被创建,然后摆放在View上面的另外两个对象UIButtonUILabel也被创建。这些对象,也就是控件,被创建的时候,都需要设置一些属性,比如说位置,颜色等等,这些信息都可以通过Interface Builder来设置,选中任意一个控件,然后组合键cmd+1cmd+3,就可以设置一些基本属性了。

?

HelloWorldViewController.xib中选中File Owner,然后cmd+2,可以看到程序中变量(m_label)是怎么和控件联系起来的,程序中的方法(showMessage)是如何与控件的事件联系起来的。

?

通过上面的解释,应该能对iPhone程序的工作原理有进一步的认识了。我们用下图来简单的表示一下这个过程:

-->MainWindow

?? ? |--> HelloWorld App Delegate

?? ??|--> Window

?? ? |--> Hello World View Controller

?? ? ? ? ? ? ? ? ?|--> UIView

?? ? ? ? ? ? ? ? ?|--> UIButton

?? ? ? ? ? ? ? ? ?|--> UILabel

?

使用window-based模板创建一个单view程序

上一篇我们使用iPhone自带的view-based模板创建了第一个程序HelloWorld。但是在实际工作中,我们最常使用的还是window-based模板,任何一个iPhone程序都可以基于这个模板构建,因为它是最通用的模板。废话不多说,我们开始吧。

?

这次我们使用window-based模板来创建上一篇中的HelloWorld。

启动xCode,File->New Project->选择window-based模板,给工程取名为HelloWorld。首先来看一看模板都提供了哪些默认的文件。


上图分别是本工程和上一个工程的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!

?

基于window-based模板的多View程序

上礼拜一直忙,没有空写blog了。。不过凡事都要坚持才能有点儿效果。
看着阅读数量的增加,表明还是有人在看我的blog,这就是我坚持的动力,呵呵。
先贴一则新闻:http://it.sohu.com/20090711/n265142243.shtml
iPhone真的要进入国内市场了么?

今天我们来看一看如何基于window-based模板来创建一个多View的程序。之前我们提到过,iPhone程序一定是一个单window多 View的程序,而平常我们也经常看到用户轻轻将手指刷过屏幕,就会有新的页面显示出来,这就是多个View之间的转换。那么,是如何实现的呢?我们开始吧!

启动xCode,File->New Project->选择window-based模板,我们这次创建的程序起名为FoodList。
首先,我们来分析一下多View程序应该是怎么样的一个框架:
前面介绍过,iPhone程序的设计是按照MVC(Model-View-Control)的模式进行的。一般来说,每一个页面(View)都会有一个Controller对应(这个Controller就是我们实现的基于UIViewController的类),用于控制这个View和用户的交互。但是在设计多View程序的时候,我们还需要一个Controller用于负责不同View之间的转换。也就是说,如果程序有2个View,他们之间要互相转换,那么它必须包括至少3个xib文件(MainWindow.xib(这个是默认有的)和表示这两个View的xib文件),同时,还至少要有 3个Controller(2个用于控制View,另外一个用于控制View之间的转换)。

今天我们要完成的程序是:首先现实一个欢迎页面,然后用户点击按钮之后,进入下一个页面,是一个食物的列表。

1)选中Classes文件夹,右键,Add->New File,选择Cocoa Touch Classes中的UIViewController,取名为SwitchViewController,从名字也能猜到这个类的用途:控制View之间的转换。
2)重复上面的步骤:添加WelcomeController和FoodListController。
3)选中Resources文件夹,右键,Add->New File,选择User Interfaces中的View XIB。取名为WelcomeView。
4)重复3),添加FoodListView。
5)双击MainWindow.xib,启动Interface Builder,然后从控件库中找到View Controller,拖到MainWindow.xib的窗口中。如图所示:



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等等。

?

让View的转换 动起来!

上一篇我们实现了基于window-based模板的多View程序。用户可以方便的在多个View之间切换。今天,我们要让这个程序更酷!我们要为View之间的切换加入动画效果。举个例子,如下图所示:


这是如何实现的呢?

首先,让我们花点时间回顾一下上一篇中的一个函数

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

}

大家先看一下代码的第一行和最后一行,它们分别是beginAnimationscommitAnimations。这两句代码之间就是我们定义的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];

怎么理解这些语句的作用呢?我们回想一下之前提过的delegateApple将很多实现的细节隐藏起来,程序员不需要干涉其中的部分;但是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),需要的动画类型(setTypesetSubtype)。Apple为我们提供了4种主类型(setType)和4种子类型(setSubtype)[参考:http://developer.apple.com/documentation/GraphicsImaging/Reference/CATransition_class/Introduction/Introduction.html]。最后,我们还需要设置动画的样式(setTimingFunction),和前面讲到的setAnimationCurve是一个概念。这里我们采用的是EaseInEaseOut,同UIViewAnimationCurveEaseInOut是一个意思。

?

注意,如果想使用CATransition,需要包含QuartzCore.framework,方法如前所述。

?

如何使用TableView

还记得上一篇中的FoodList么?我们简单的用了一个UILabel把所有的食物列举出来,不得不说,这太丑陋了。

iPhone为我们提供了一个强有力的控件,来帮助我们显示数据,就是TableView!

下面这个gmail的界面就是使用TableView制作的。


好了,言归正传,我们来看看如何在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的功能等等。

?

?

?

?

?

?

?

?

?

?

?

  相关解决方案