本节书摘来自异步社区《iOS 8开发指南(第2版)》一书中的第6章,第6.5节实战演练——使用模板Single View Application,作者 管蕾,更多章节内容可以访问云栖社区“异步社区”公众号查看
6.5 实战演练——使用模板Single View Application
iOS 8开发指南(第2版)
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所示。
(3)在弹出的新界面中选择项目类型和模板。在New Project窗口的左侧,确保选择了项目类型iOS中的Application,在右边的列表中选择Single View Application,再单击“Next”按钮,如图6-3所示。
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
{
// Use this method to release shared resources, save user data, invalidate timers, 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
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See
// also applicationDidEnterBackground:.
}
@end
上述两个文件的代码都是自动生成的。
文件ViewController.h和ViewController.m实现了一个视图控制器(UIViewController),这个类包含控制视图的逻辑。一开始这些文件几乎是空的,只有一个基本结构,此时如果您单击Xcode窗口顶部的Run按钮,应用程序将编译并运行,运行后一片空白,如图6-4所示。
注意:
如果在Xcode中新建项目时指定了类前缀,所有类文件名都将以您指定的内容打头。在以前的Xcode版本中,Apple将应用程序名作为类的前缀。要让应用程序有一定的功能,需要处理前面讨论过的两个地方:视图和视图控制器。
2.故事板文件
除了类文件之外,该项目还包含了一个故事板文件,它用于存储界面设计。单击故事板文件 MainStoryboardstoryboard,在Interface Builder编辑器中打开它,如图6-5所示。
在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所示。
如果有多个场景,在Interface Builder编辑器中会使用很不明显的方式指定了初始场景。在前面的图6-6中,会发现编辑器中有一个灰色箭头,它指向视图的左边缘。这个箭头是可以拖动的,当有多个场景时可以拖动它,使其指向任何场景对应的视图。这就自动配置了项目,使其在应用程序启动时启动该场景的视图控制器和视图。
总之,对应用程序进行了配置,使其加载MainStoryboard.storyboard,而MainStoryboard.story-board查找初始场景,并创建该场景的视图控制器类(文件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所示。
(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.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所示。
1.添加输出口
下面首先连接用于显示输出的标签。前面说过,我们想用一个名为userOutput的实例变量/属性表示它。
(1)按住Control键,并拖曳用于输出的标签(在这里,其标题为<请输入信息>)或文档大纲中表示它的图标。将其拖曳到包含文件ViewController.h的代码编辑器中,当鼠标光标位于@interface行下方时松开鼠标键。当您拖曳时,Xcode将指出如果您此时松开鼠标键将插入什么,如图6-10所示。
(2)当松开鼠标键时会要求我们定义输出口。接下来首先确保从下拉列表Connection中选择了Outlet,从Storage下拉列表中选择了Strong,并从Type下拉列表中选择了UILabel。最后指定要使用的实例“变量/属性”名(userOutput),最后再单击Connect按钮,如图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.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)interfaceOrientation
{
// 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所示。