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

 
// 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 中的方法。每当我们创建一个自己的工程的时候,都会在ProjectName AppDelegate(注: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的功能等等。