第6章 使用Xcode编写MVC程序

在本书前面的内容中,已经学习了面向对象编程语言Objective-C的基本知识,并且探索了Cocoa Touch、Xcode和Interface Builder编辑器的基本用法。虽然我们已经使用了多个创建好的项目,但是还没有从头开始创建一个项目。在本章的内容中,将向读者详细讲解“模型—视图—控制器”应用程序的设计模式,并从头到尾创建一个iOS应用程序的过程,为读者步入本书后面知识的学习打下基础。

6.1 MVC模式基础

当我们开始编程时,会发现每一个功能都可以用多种编码方式来实现。但是究竟哪一种方式才是最佳选择呢?在开发iOS应用程序的过程中,通常使用的设计方法被称为“模型—视图—控制器”模式,这种模式被简称为MVC,通过这种模式可以帮助我们创建出简洁、高效的应用程序。

6.1.1 诞生背景

在创建与用户交互的应用程序时,首先必须考虑如下3点。

用户界面:我们必须提供让用户能够与之交互的东西,例如按钮和文本框等。

对用户输入进行处理并做出反应。

应用程序必须存储必要的信息以便正确地响应用户,这通常是以数据库方式存储的。

为了结合这几个方面,一种方法是将它们合并到一个类中:将显示界面的代码、实现逻辑的代码以及处理数据的代码混合在一起。这是一种非常直观的开发方法,但在多个方面束缚了开发人员。

当众多的代码混合在一起时,多个开发人员难以配合,因为功能单元之间没有明确的界线。不太可能在其他应用程序中重用界面、应用程序逻辑和数据,因为这三方面的组合因项目而异,在其他地方不会有大的用处。总之,混合代码、逻辑和数据将导致混乱。而我们希望iOS应用程序与此相反,解决之道便是使用MVC设计模式。

6.1.2 分析结构

MVC最初存在于Desktop程序中,M是指数据模型,V是指用户界面,C则是控制器。使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

MVC即“模型-视图-控制器”,是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已被广泛使用,特别是ColdFusion和PHP的开发者。

MVC是一个设计模式,它能够强制性地使应用程序的输入、处理和输出分开。使用MVC的应用程序被分成3个核心部件,分别是模型、视图、控制器。具体说明如下所示。

1.视图

视图是用户看到并与之交互的界面。对于老式的Web应用程序来说,视图就是由HTML元素组成的界面。在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色,但一些新的技术已层出不穷,它们包括Adobe Flash和像XHTML、XML/XSL、WML等一些标识语言和Web Services。如何处理应用程序的界面变得越来越有挑战性。MVC一个大的好处是它能为你的应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,不管这些数据是联机存储的还是一个雇员列表,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。

2.模型

模型表示企业数据和业务规则。在MVC的3个部件中,模型拥有最多的处理任务。例如它可能用像EJBs和ColdFusion Components这样的构件对象来处理数据库。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。

3.控制器

控制器用于接受用户的输入并调用模型和视图去完成用户的需求。所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后确定用哪个视图来显示模型处理返回的数据。

现在我们总结MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。

6.1.3 MVC的特点

MVC是所有面向对象程序设计语言都应该遵守的规范,MVC思想将一个应用分成三个基本部分:Model(模型)、View(视图)和Controller(控制器),这三个部分以最少的耦合协同工作,从而提高了应用的可扩展性及可维护性。

在经典的MVC模式中,事件由控制器处理,控制器根据事件的类型改变模型或视图。具体来说,每个模型对应一系列的视图列表,这种对应关系通常采用注册来完成,即把多个视图注册到同一个模型,当模型发生改变时,模型向所有注册过的视图发送通知,然后视图从对应的模型中获得信息,然后完成视图显示的更新。

MVC模式具有如下4个特点。

(1)多个视图可以对应一个模型。按MVC设计模式,一个模型对应多个视图,可以减少代码的复制及代码的维护量,一旦模型发生改变易于维护。

(2)模型返回的数据与显示逻辑分离。模型数据可以应用任何的显示技术,例如,使用JSP页面、Velocity模板或者直接产生Excel文档等。

(3)应用被分隔为3层,降低了各层之间的耦合,提供了应用的可扩展性。

(4)因为在控制层中把不同的模型和不同的视图组合在一起完成不同的请求,由此可见,控制层包含了用户请求权限的概念。

MVC更符合软件工程化管理的精神。不同的层各司其职,每一层的组件具有相同的特征,有利于通过工程化和工具化产生管理程序代码。

6.1.4 使用MVC实现程序设计的结构化

通过使用MVC模式,在应用程序的重要组件之间定义了明确的界线。MVC模式定义了应用程序的如下3个部分。

(1)模型提供底层数据和方法,它向应用程序的其他部分提供信息。模型没有定义了应用程序的外观和工作方式。

(2)用户界面由一个或多个视图组成,而视图由不同的屏幕控件(按钮、文本框、开关等)组成,用户可与之交互。

(3)控制器通常与视图配对,负责接受用户输入并做出相应的反应。控制器可访问视图并使用模型提供的信息更新它,还可使用用户在视图中的交互结果来更新模型。总之,它在MVC组件之间搭建了桥梁。

令我们开发者振奋的是,Xcode中的MVC模式是天然存在的,当我们新建项目并开始编码时,会自动被引领到MVC设计模式。由此可见,在Xcode开发环境中可以很容易地创建结构良好的应用程序。

6.2 Xcode中的MVC

在用Xcode编程并在Interface Builder中安排用户界面(UI)元素后,Cocoa Touch的结构旨在利用MVC(Model-View-Controller,模型—视图—控制器)设计模式。在本节的内容中,将讲解Xcode中MVC模式的基本知识。

6.2.1 原理

MVC模式会将Xcode项目分为如下3个不同的模块。

1.模型

模型是应用程序的数据,比如项目中的数据模型对象类。模型还包括采用的数据库架构,比如CoreData或者直接使用SQLite文件。

2.视图

顾名思义,视图是用户看到的应用程序的可视界面。它包含在Interface Builder中构建的各种UI组件。

3.控制器

控制器是将模型和视图元素连接在一起的逻辑单元,处理用户输入和UI交互。UIKit组件的子类,比如UINavigationController和UITabBarController是最先会被想到的,但是这一概念还扩展到了应用程序委托和NSObject的自定义子类。

虽然在Xcode项目中,上述3个MVC元素之间会有大量交互,但是创建的代码和对象应该简单地定义为仅属于三者之一。当然,完全在代码内生成UI或者将所有数据模型方法存储在控制器类中非常简单,但是如果你的源代码没有良好的结构,会使模型、视图和控制器之间的分界线变得非常模糊。

另外,这些模式的分离还有一个很大的好处是可重用性!在iPad出现之前,应用程序的结构可能不是很重要,特别是不打算在其他项目中重用任何代码的时候。过去我们只为一个规格的设备(iPhone320×480的小屏幕)开发应用程序。但是现在需要将应用程序移植到iPad上,利用平板电脑的新特性和更大的屏幕尺寸。如果iPhone应用程序不遵循MVC设计模式,那么将Xcode项目移植到iPad上会立刻成为一项艰巨的任务,需要重新编写很多代码才能生成一个iPad增强版。

例如,假设根视图控制器类包含所有代码,这些代码不仅用于通过Core Data获取数据库记录,还会动态生成UINavigationController以及一个嵌套的UITableView用于显示这些记录。这些代码在iPhone上可能会良好运行,但是迁移到iPad上后可能想用UISplitViewController来显示这些数据库记录。但是此时需要手动去除所有UINavigationController代码,这样才能添加新的UISplitViewController功能。但是如果将数据类(模型)与界面元素(视图)和控制器对象(控制器)分开,那么将项目移植到iPad的过程会非常轻松。

6.2.2 模板就是给予MVC的

Xcode提供了若干模板,这样可以在应用程序中实现MVC架构。

1.view-based application(基于视图的应用程序)

如果应用程序仅使用一个视图,建议使用这个模板。一个简单的视图控制器会管理应用程序的主视图,而界面设置则使用一个Interface Builder模板来定义。特别是那些未使用任何导航功能的简单应用程序应该使用这个模板。如果应用程序需要在多个视图之间切换,建议考虑使用基于导航的模板。

2.navigation-based application(基于导航的应用程序)

基于导航的模板用在需要多个视图之间进行间切换的应用程序。如果可以预见在应用程序中,会有某些画面上带有一个“回退”按钮,此时就应该使用这个模板。导航控制器会完成所有关于建立导航按钮以及在视图“栈”之间切换的内部工作。这个模板提供了一个基本的导航控制器以及一个用来显示信息的根视图(基础层)控制器。

3.utility application(工具应用程序)

适合于微件(Widget)类型的应用程序,这种应用程序有一个主视图,并且可以将其“翻”过来,例如iPhone中的天气预报和股票程序等就是这类程序。这个模板还包括一个信息按钮,可以将视图翻转过来显示应用程序的反面,这部分常常用来对设置或者显示的信息进行修改。

4.OpenGL ES application(OpenGL ES应用程序)

在创建3D游戏或者图形时可以使用这个模板,它会创建一个配置好的视图,专门用来显示GL场景,并提供了一个例子计时器可以令其演示动画。

5.tab bar application(标签栏应用程序)

提供了一种特殊的控制器,会沿着屏幕底部显示一个按钮栏。这个模板适用于像iPod或者电话这样的应用程序,它们都会在底部显示一行标签,提供一系列的快捷方式,来使用应用程序的核心功能。

6.window-based application(基于窗口的应用程序)

提供了一个简单的、带有一个窗口的应用程序。这是一个应用程序所需的最小框架,可以用它作为开始来编写自己的程序。

6.3 在Xcode中实现MVC

在本书前面的内容中,已经讲解了Xcode及其集成的Interface Builder编辑器的知识。并且在本书上一章的内容中,曾经将故事板场景中的对象连接到了应用程序中的代码。在本节的内容中,将详细讲解将视图绑定到控制器的知识。

6.3.1 视图

在Xcode中,虽然可以使用编程的方式创建视图,但是在大多数情况下是使用Interface Builder以可视化的方式设计它们。在视图中可以包含众多界面元素,在加载运行阶段程序时,视图可以创建基本的交互对象,例如,当轻按文本框时会打开键盘。要让想视图中的对象能够与应用程序实现逻辑交互,必须定义相应的连接。连接的东西有两种:输出口和操作。输出口定义了代码和视图之间的一条路径,可以用于读写特定类型的信息,例如对应于开关的输出口让我们能够访问描述开关是开还是关的信息;而操作定义了应用程序中的一个方法,可以通过视图中的事件触发,例如轻按按钮或在屏幕上轻扫。

如果将输出口和操作连接到代码呢?必须在实现视图逻辑的代码(即控制器)中定义输出口和操作。

6.3.2 视图控制器

控制器在Xcode中被称为视图控制器,功能是负责处理与视图的交互工作,并为输出口和操作之间建立一个人为连接。为此需要在项目代码中使用两个特殊的编译指令:IBAction和IBOutlet。IBAction和IBOutlet是Interface Builder能够识别的标记,它们在Objective-C中没有其他用途。我们在视图控制器的接口文件中添加这些编译指令。我们不但可以手工添加,而且也可以用Interface Builder的一项特殊功能自动生成它们。

注意:视图控制器可包含应用程序逻辑,但这并不意味着所有代码都应包含在视图控制器中。虽然在本书中,大部分代码都放在视图控制器中,但当您创建应用程序时,可在合适的时候定义额外的类,以抽象应用程序逻辑。

1.使用IBOutlet

IBOutlet对于编译器来说是一个标记,编译器会忽略这个关键字。Interface Builder则会根据IBOutlet来寻找可以在Builder里操作的成员变量。在此需要注意的是,任何一个被声明为IBOutlet并且在InterfaceBuilder里被连接到一个UI组件的成员变量,会被额外记忆一次,例如:

IBOutlet UILabel *label;

这个label在Interface Builder里被连接到一个UILabel。此时,这个label的retainCount为2。所以,只要使用了IBOutlet变量,一定需要在dealloc或者viewDidUnload中释放这个变量。

IBOutlet的功能是让代码能够与视图中的对象交互。假设在视图中添加了一个文本标签(UILabel),而我们想在视图控制器中创建一个实例“变量/属性”myLabel。此时可以显式地声明它们,也可使用编译指令@property隐式地声明实例变量,并添加相应的属性:

@property (strong, nonatomic) UILabel *myLabel;

这个应用程序提供了一个存储文本标签引用的地方,还提供了一个用于访问它的属性,但还需将其与界面中的标签关联起来。为此,可在属性声明中包含关键字IBOutlet:

@property (strong, nonatomic) IBOutlet UILabel *myLabel;

添加该关键字后,就可以在Interface Builder中以可视化方式将视图中的标签对象连接到变量/属性MyLabel,然后可以在代码中使用该属性与该标签对象交互:修改其文本、调用其方法等。这样,这行代码便声明了实例变量、属性和输出口。

2.使用编译指令property和synthesize简化访问

@property和@synthesize是Objective-C语言中的两个编译指令。实例变量存储的值或对象引用可在类的任何地方使用。如果需要创建并修改一个在所有类方法之间共享的字符串,就应声明一个实例变量来存储它。良好的编程惯例是,不直接操作实例变量。所以要使用实例变量,需要有相应的属性。

编译指令@property定义了一个与实例变量对应的属性,该属性通常与实例变量同名。虽然可以先声明一个实例变量,再定义对应的属性,但是也可以使用@property隐式地声明一个与属性对应的实例变量。例如要声明一个名为myString的实例变量(类型为NSString)和相应的属性,可以编写如下所示的代码实现:

@property (strong, nonatomic) NSString *myString;

这与下面两行代码等效:

NSString *myString;

@property (strong, nonatomic) NSString *myString;

注意:Apple Xcode工具通常建议隐式地声明实例变量,所以建议大家也这样做。

这同时创建了实例变量和属性,但是要想使用这个属性则必须先合成它。编译指令@synthesize创建获取函数和设置函数,让我们很容易访问和设置底层实例变量的值。对于接口文件(.h)中的每个编译指令@property,实现文件( .m)中都必须有对应的编译指令@synthesize:

@synthesize myString;

3.使用IBAction

IBAction用于指出在特定的事件发生时应调用代码中相应的方法。假如按下了按钮或更新了文本框,则可能想应用程序采取措施并做出合适的反应。编写实现事件驱动逻辑的方法时,可在头文件中使用IBAction声明它,这将向Interface Builder编辑器暴露该方法。在接口文件中声明方法(实际实现前)被称为创建方法的原型。

例如,方法doCalculation的原型可能类似于下面的情形:

-(IBAction)doCalculation: (id) sender;

注意到该原型包含一个sender参数,其类型为id。这是一种通用类型,当不知道(或不需要知道)要使用的对象的类型时可以使用它。通过使用类型id,可以编写不与特定类相关联的代码,使其适用于不同的情形。创建将用作操作的方法(如doCalculation)时,可以通过参数sender确定调用了操作的对象并与之交互。如果要设计一个处理多种事件(如多个按钮中的任何一个按钮被按下)的方法,这将很方便。

6.4 数据模型

Core Data抽象了应用程序和底层数据存储之间的交互。它还包含一个Xcode建模工具,该工具像Interface Builder那样可帮助我们设计应用程序,但不是让我们能够以可视化的方式创建界面,而是让我们以可视化方式建立数据结构。Core Data是Cocoa中处理数据、绑定数据的关键特性,其重要性不言而喻,但也比较复杂。

下面先给出一张如图6-1所示的类关系图。

图6-1 类关系图

在图6-1中,我们可以看到有如下五个相关的模块。

(1)Managed Object Model。

Managed Object Model是描述应用程序的数据模型,这个模型包含实体(Entity)、特性(Property),读取请求(Fetch Request)等。

(2)Managed Object Context。

Managed Object Context参与对数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对 undo/redo 的支持及更新绑定到数据的 UI。

(3)Persistent Store Coordinator。

Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入,一般我们无需与它打交道。

(4)Managed Object Managed Object数据对象。

与 Managed Object Context相关联。

(5)Controller图中绿色的Array Controller、Object Controller和Tree Controller。

这些控制器一般都是通过“control+drag”将Managed Object Context绑定到它们,这样就可以在 nib中以可视化地方式操作数据。

上述模块的运作流程如下所示。

(1)应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。Document应用程序是一般是通过 NSDocument 或其子类 NSPersistentDocument)从模型文件(后缀为xcdatamodeld)读取。

(2)然后生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 对象,前者对用户透明地调用后者对数据文件进行读写。

(3)NSPersistentStoreCoordinator从数据文件(XML、SQLite、二进制文件等)中读取数据生成 Managed Object,或保存Managed Object写入数据文件。

(4)NSManagedObjectContext对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是最常被用到的。

(5)Array Controller、Object Controller和Tree Controller等控制器一般与 NSManagedObjectContext 关联,因此可以通过它们在nib 中可视化地操作数据对象。

6.5 实战演练——使用模板Single View Application

Apple在Xcode中提供了一种很有用的应用程序模板,可以快速地创建一个这样的项目,即包含一个故事板、一个空视图和相关联的视图控制器。模板Single View Application(单视图应用程序)是最简单的模板,在本节的内容中将创建一个应用程序,本程序包含了一个视图和一个视图控制器。本节的实例非常简单,先创建了一个用于获取用户输入的文本框(UITextField)和一个按钮,当用户在文本框中输入内容并按下按钮时,将更新屏幕标签(UILabel)以显示Hello和用户输入。虽然本实例程序比较简单,但是几乎包含了本章讨论的所有元素:视图、视图控制器、输出口和操作。

6.5.1 创建项目

首先在Xcode中新建一个项目,并将其命名为“hello”。

(1)从文件夹Developer/Applications或Launchpad的Developer编组中启动Xcode。

(2)启动后在左侧导航选择第一项“Create a new Xcode project”,如图6-2所示。

图6-2 新建一个 Xcode工程

(3)在弹出的新界面中选择项目类型和模板。在New Project窗口的左侧,确保选择了项目类型iOS中的Application,在右边的列表中选择Single View Application,再单击“Next”按钮,如图6-3所示。

图6-3 选择Single View Application

1.类文件

展开项目代码编组(名为HelloNoun),并查看其内容。会看到如下5个文件:

AppDelegate.h;

AppDelegate.m;

ViewController.h;

ViewController.m;

MainStoryboard.storyboard。

其中文件AppDelegate.h和AppDelegate.m组成了该项目将创建的UIApplication实例的委托,也就是说我们可以对这些文件进行编辑,以添加控制应用程序运行时如何工作的方法。我们可以修改委托,在启动时执行应用程序级设置、告诉应用程序进入后台时如何做以及应用程序被迫退出时该如何处理。就本章这个演示项目来说,我们不需要在应用程序委托中编写任何代码,但是需要记住它在整个应用程序生命周期中扮演的角色。

其中文件AppDelegate.h的代码如下:

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

文件AppDelegate.m的代码如下所示:

//

// AppDelegate.m

// hello

//

// Created by on 12-9-17.

// Copyright (c) 2012年 apple. All rights reserved.

//

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:

(NSDictionary *)launchOptions

{

//Override point for customization after application launch.

return YES;

}

- (void)applicationWillResignActive:(UIApplication *)application

{

//Sent when the application is about to move from active to inactive state. This

can occur for certain types of temporary interruptions (such as an incoming phone call

or SMS message) or when the user quits the application and it begins the transition to

the background state.

// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL

ES frame rates. Games should use this method to pause the game.

}

- (void)applicationDidEnterBackground:(UIApplication *)application

{

//Usethismethodtoreleasesharedresources,saveuserdata,invalidatetimers,

and store enough application state information to restore your application to its current

state in case it is terminated later.

// If your application supports background execution, this method is called instead

of applicationWillTerminate: when the user quits.

}

- (void)applicationWillEnterForeground:(UIApplication *)application

{

//Calledaspartofthetransitionfromthebackgroundtotheinactivestate;here

you can undo many of the changes made on entering the background.

}

- (void)applicationDidBecomeActive:(UIApplication *)application

{

//Restartanytasksthatwerepaused(ornotyetstarted)whiletheapplicationwas

inactive. If the application was previously in the background, optionally refresh the

user interface.

}

- (void)applicationWillTerminate:(UIApplication *)application

{

//Calledwhentheapplicationisabouttoterminate.Savedataifappropriate.See

// also applicationDidEnterBackground:.

}

@end

上述两个文件的代码都是自动生成的。

文件ViewController.h和ViewController.m实现了一个视图控制器(UIViewController),这个类包含控制视图的逻辑。一开始这些文件几乎是空的,只有一个基本结构,此时如果您单击Xcode窗口顶部的Run按钮,应用程序将编译并运行,运行后一片空白,如图6-4所示。

图6-4 执行后为空

注意:如果在Xcode中新建项目时指定了类前缀,所有类文件名都将以您指定的内容打头。在以前的Xcode版本中,Apple将应用程序名作为类的前缀。要让应用程序有一定的功能,需要处理前面讨论过的两个地方:视图和视图控制器。

2.故事板文件

除了类文件之外,该项目还包含了一个故事板文件,它用于存储界面设计。单击故事板文件 MainStoryboardstoryboard,在Interface Builder编辑器中打开它,如图6-5所示。

图6-5 MainStoryboardstoryboard界面

在MainStoryboard.storyboard界面中包含了如下3个图标:

First Responder(一个UIResponder实例);

View Controller(我们的ViewController类);

应用程序视图(一个UIView实例)。

视图控制器和第一响应者还出现在图标栏中,该图标栏位于编辑器中视图的下方。如果在该图标栏中没有看到图标,只需单击图标栏,它们就会显示出来。

当应用程序加载故事板文件时,其中的对象将被实例化,成为应用程序的一部分。就本项目“hello”来说,当它启动时会创建一个窗口并加载MainStoryboard.storyboard,实例化ViewController类及其视图,并将其加入到窗口中。

在文件HelloNoun-Info.plist中,通过属性Main storyboard file base name(主故事板文件名)指定了加载的文件是MainStoryboard.storyboard。要想核实这一点,读者可展开文件夹Supporting Files,再单击plist文件显示其内容。另外也可以单击项目的顶级图标,确保选择了目标“hello”,再查看选项卡Summary中的文本框Main Storyboard,如图6-6所示。

图6-6 指定应用程序启动时将加载的故事板

如果有多个场景,在Interface Builder编辑器中会使用很不明显的方式指定了初始场景。在前面的图6-6中,会发现编辑器中有一个灰色箭头,它指向视图的左边缘。这个箭头是可以拖动的,当有多个场景时可以拖动它,使其指向任何场景对应的视图。这就自动配置了项目,使其在应用程序启动时启动该场景的视图控制器和视图。

总之,对应用程序进行了配置,使其加载MainStoryboard.storyboard,而MainStoryboard.storyboard查找初始场景,并创建该场景的视图控制器类(文件ViewController.h和ViewController.m定义的ViewController)的实例。视图控制器加载其视图,而视图被自动添加到主窗口中。

6.5.2 规划变量和连接

要创建该应用程序,第一步是确定视图控制器需要的东西。为引用要使用的对象,必须与如下3个对象进行交互:

一个文本框(UITextField);

一个标签(UILabel);

一个按钮(UIButton)。

其中前两个对象分别是用户输入区域(文本框)和输出(标签),而第3个对象(按钮)触发代码中的操作,以便将标签的内容设置为文本框的内容。

1.修改视图控制器接口文件

基于上述信息,便可以编辑视图控制器类的接口文件(ViewController.h),在其中定义需要用来引用界面元素的实例变量以及用来操作它们的属性(和输出口)。我们将把用于收集用户输入的文本框(UITextField)命名为user@property将提供输出的标签(URLabel)命名为userOutput。前面说过,通过使用编译指令@property可同时创建实例变量和属性,而通过添加关键字IBoutlet可以创建输出口,以便在界面和代码之间建立连接。

综上所述,可以添加如下两行代码:

@property (strong, nonatomic) IBOutlet UILabel *userOutput;

@property (strong, nonatomic) IBOutlet UITextField *userInput;

为了完成接口文件的编写工作,还需添加一个在按钮被按下时执行的操作。我们将该操作命名为setOutput:

- (IBAction)setOutput: (id)sender;

添加这些代码后,文件ViewController.h的代码如下所示。其中以粗体显示的代码行是我们新增的:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) IBOutlet UILabel *userOutput;

@property (strong, nonatomic) IBOutlet UITextField *userInput;

- (IBAction)setOutput:(id)sender;

@end

但是这并非我们需要完成的全部工作。为了支持我们在接口文件中所做的工作,还需对实现文件(ViewController.m)做一些修改。

2.修改视图控制器实现文件

对于接口文件中的每个编译指令@property来说,在实现文件中都必须有如下对应的编译指令@synthesize:

@synthesize userInput;

@synthesize userOutput;

将这些代码行加入到实现文件开头,并位于编译指令@implementation后面,文件ViewController.m中对应的实现代码如下所示:

#import "ViewController.h"

@implementation ViewController

@synthesize userOutput;

@synthesize userInput;

在确保使用完视图后,应该使代码中定义的实例变量(即userInput 和userOutput)不再指向对象,这样做的好处是这些文本框和标签占用的内存可以被重复重用。实现这种方式的方法非常简单,只需将这些实例变量对应的属性设置为nil即可:

[self setUserlnput:nil];

[self setUserOutput:nil];

上述清理工作是在视图控制器的一个特殊方法中进行的,这个方法名为viewDidUnload,在视图成功地从屏幕上删除时被调用。为添加上述代码,需要在实现文件ViewController.h中找到这个方法,并添加代码行。同样,这里演示的是如果要手工准备输出口、操作、实例变量和属性时,需要完成的设置工作。

文件ViewController.m中对应清理工作的实现代码如下所示:

- (void)viewDidUnload

{

self.userInput=nil;

self.userOutput = nil;

[self setUserOutput:nil];

[self setUserInput:nil];

[super viewDidUnload];

// Release any retained subviews of the main view.

// e.g. self.myOutlet = nil;

}

注意:如果浏览HelloNoun的代码文件,可能发现其中包含绿色的注释(以字符“//”打头的代码行)。为节省篇幅,通常在本书的程序清单中删除了这些注释。

3.一种简化的方法

虽然还没有输入任何代码,但还是希望能够掌握规划和设置Xcode项目的方法。所以还需要做如下所示的工作。

确定所需的实例变量:哪些值和对象需要在类(通常是视图控制器)的整个生命周期内都存在。

确定所需的输出口和操作:哪些实例变量需要连接到界面中定义的对象?界面将触发哪些方法?

创建相应的属性:对于您打算操作的每个实例变量,都应使用@property来定义实例变量和属性,并为该属性合成设置函数和获取函数。如果属性表示的是一个界面对象,还应在声明它时包含关键字IBOutlet。

清理:对于在类的生命周期内不再需要的实例变量,使用其对应的属性将其值设置为nil。对于视图控制器中,通常是在视图被卸载时(即方法viewDidUnload中)这样做。

当然我们可以可手工完成这些工作,但是在Xcode中使用Interface Builder编辑能够在建立连接时添加编译指令@property和@synthesize、创建输出口和操作、插入清理代码。

将视图与视图控制器关联起来的是前面介绍的代码,但您可在创建界面的同时让Xcode自动为我们编写这些代码。创建界面前,仍然需要确定要创建的实例变量/属性、输出口和操作,而有时候还需添加一些额外的代码,但让Xcode自动生成代码可极大地加快初始开发阶段的进度。

6.5.3 设计界面

添加对象

本节的演示程序“hello”的界面很简单,只需提供一个输出区域、一个用于输入的文本框以及一个将输出设置成与输入相同的按钮。请按如下步骤创建该UI。

(1)在Xcode项目导航器中选择MainStoryboard.storyboard,并打开它。

(2)打开它的是Interface Builder编辑器。其中文档大纲区域显示了场景中的对象,而编辑器中显示了视图的可视化表示。

(3)选择菜单View>Utilities>Show Object Library(Control+Option+Command+3),在右边显示对象库。在对象库中确保从下拉列表中选择了Objects,这样将显示可拖放到视图中的所有控件,此时的工作区类似于图6-7所示。

图6-7 初始界面

(4)通过在对象库中单击标签(UILabel)对象并将其拖曳到视图中,在视图中添加两个标签。

(5)第一个标签应包含静态文本Hello,为此该标签的双击默认文本Label并将其改为“你好”。选择第二个标签,它将用作输出区域。这里将该标签的文本改为“请输入信息”。将此作为默认值,直到用户提供新字符串为止。我们可能需要增大该文本标签以便显示这些内容,为此可单击并拖曳其手柄。

我们还要将这些标签居中对齐,此时可以通过单击选择视图中的标签,再按下Option+ Command+4或单击Utility区域顶部的滑块图标,这将打开标签的Attributes Inspector。

使用Alignment选项调整标签文本的对齐方式。另外还可能会使用其他属性来设置文本的显示样式,例如字号、阴影、颜色等。现在整个视图应该包含两个标签。

(6)如果对结果满意,便可以添加用户将与之交互的元素文本框和按钮。为了添加文本框,在对象库中找到文本框对象(UITextField),单击并将其拖曳到两个标签下方。使用手柄将其增大到与输出标签等宽。

(7)再次按Option+Command+4打开Attributes Inspector,并将字号设置成与标签的字号相同。注意到文本框并没有增大,这是因为默认iPhone文本框的高度是固定的。要修改文本框的高度,在Attributes Inspector中单击包含方形边框的按钮Border Style,然后便可随意调整文本框的大小。

(8)在对象库单击圆角矩形按钮(UIButton)并将其拖曳到视图中,将其放在文本框下方。双击该按钮给它添加一个标题,如Set Label;再调整按钮的大小,使其能够容纳该标题。您也可能想使用Attributes Inspector增大文本的字号。

最终UI界面效果如图6-8所示,其中包含了4个对象,分别是两个标签、1个文本框和1个按钮。

图6-8 最终的UI界面

6.5.4 创建并连接输出口和操作

现在,在Interface Builder编辑器中需要做的工作就要完成了,最后一步工作是将视图连接到视图控制器。如果按前面介绍的方式手工定义了输出口和操作,则只需在对象图标之间拖曳即可。但即使就地创建输出口和操作,也只需执行拖放操作。

为此,需要从Interface Builder编辑器拖放到代码中这需要添加输出口或操作的地方,即需要能够同时看到接口文件VeiwController.h和视图。在Interface Builder编辑器中还显示了刚设计的界面的情况下,单击工具栏的Edit部分的Assistant Editor按钮,这将在界面右边自动打开文件ViewController.h,因为Xcode知道我们在视图中必须编辑该文件。

另外,如果我们使用的开发计算机是MacBook,或编辑的是iPad项目,屏幕空间将不够用。为了节省屏幕空间,单击工具栏中View部分最左边和最右边的按钮,以隐藏Xcode窗口的导航区域和Utility区域。您也可以单击Interface Builder编辑器左下角的展开箭头将文档大纲区域隐藏起来。这样屏幕将类似于如图6-9所示。

图6-9 切换工作空间

1.添加输出口

下面首先连接用于显示输出的标签。前面说过,我们想用一个名为userOutput的实例变量/属性表示它。

(1)按住Control键,并拖曳用于输出的标签(在这里,其标题为<请输入信息>)或文档大纲中表示它的图标。将其拖曳到包含文件ViewController.h的代码编辑器中,当鼠标位于@interface行下方时松开。当您拖曳时,Xcode将指出如果您此时松开鼠标将插入什么,如图6-10所示。

图6-10 生成代码

(2)当松开鼠标时会要求我们定义输出口。接下来首先确保从下拉列表Connection中选择了Outlet,从Storage下拉列表中选择了Strong,并从Type下拉列表中选择了UILabel。最后指定我们要使用的实例“变量/属性”名(userOutput),最后再单击Connect按钮,如图6-11所示。

图6-11 配置创建的输出口

(3)当单击Connect按钮时,Xcode将自动插入合适的编译指令@property和关键字IBOut:put(隐式地声明实例变量)、编译指令@synthesize(插入到文件ViewController.m中)以及清理代码(也是文件ViewController.m中)。更重要的是,还在刚创建的输出口和界面对象之间建立连接。

(4)对文本框重复上述操作过程。将其拖曳至刚插入的@property代码行下方,将Type设置为UITextField,并将输出口命名为userInput。

2.添加操作

添加操作并在按钮和操作之间建立连接的方式与添加输出口相同。唯一的差别是在接口文件中,操作通常是在属性后面定义的,因此您需要拖放到稍微不同的位置。

(1)按住Control键,并将视图中的按钮拖曳到接口文件(ViewController.h)中刚添加的两个@property编译指令下方。同样,当您拖曳时,Xcode将提供反馈,指出它将在哪里插入代码。拖曳到要插入操作代码的地方后,松开鼠标。

(2)与输出口一样,Xcode将要求您配置连接,如图6-12所示。这次,务必将连接类型设置为Action,否则Xcode将插入一个输出口。将Name(名称)设置为setOutput(前面选择的方法名)。务必从下拉列表Event中选择Touch Up Inside,以指定将触发该操作的事件。保留其他默认设置,并单击Connect按钮。

图6-12 配置要插入到代码中的操作

到此为止,我们成功添加了实例变量、属性、输出口,并将它们连接到了界面元素。在最后我们还需要重新配置我们的工作区,确保项目导航器可见。

6.5.5 实现应用程序逻辑

创建好视图并建立到视图控制器的连接后,接下来的唯一任务便是实现逻辑。现在将注意力转向文件ViewController.m以及setOutput的实现上。setOutput方法将输出标签的内容设置为用户在文本框中输入的内容。我们如何获取并设置这些值呢?UILabel和UITextField都有包含其内容的text属性,通过读写该属性,只需一个简单的步骤便可将userOutput的内容设置为userInput的内容。

打开文件ViewController.m并滚动到末尾,会发现Xcode在创建操作连接代码时自动编写了空的方法定义(这里是setOutput),我们只需填充内容即可。找到方法setOutput,其实现代码如下所示:

- (IBAction)setOutput:(id)sender {

// [[self userOutput]setText:[[self userInput] text]];

self.userOutput.text=self.userInput.text;

}

通过这条赋值语句便完成了所有的工作。

接下来我们整理核心文件ViewController.m的实现代码:

#import "ViewController.h"

@implementation ViewController

@synthesize userOutput;

@synthesize userInput;

- (void)didReceiveMemoryWarning

{

[super didReceiveMemoryWarning];

// Release any cached data, images, etc that aren't in use.

}

#pragma mark - View lifecycle

- (void)viewDidLoad

{

[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

}

- (void)viewDidUnload

{

self.userInput=nil;

self.userOutput = nil;

[self setUserOutput:nil];

[self setUserInput:nil];

[super viewDidUnload];

// Release any retained subviews of the main view.

// e.g. self.myOutlet = nil;

}

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

}

- (void)viewDidAppear:(BOOL)animated

{

[super viewDidAppear:animated];

}

- (void)viewWillDisappear:(BOOL)animated

{

[super viewWillDisappear:animated];

}

- (void)viewDidDisappear:(BOOL)animated

{

[super viewDidDisappear:animated];

}

-

(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrien

tation

{

//Return YES for supported orientations

return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

}

- (IBAction)setOutput:(id)sender {

// [[self userOutput]setText:[[self userInput] text]];

self.userOutput.text=self.userInput.text;

}

@end

上述代码几乎都是用Xcode自动实现的。

6.5.6 生成应用程序

现在可以生成并测试我们的演示程序了,执行后的效果如图6-13所示。在文本框中输入信息并单击“单击我”按钮后,会在上方显示我们输入的文本,如图6-14所示。

图6-13 执行效果

图6-14 显示输入的信息