iOS和相关库的开发大量使用了模型-视图-控制器(MVC)模式。一般而言,MVC是一种策略,用于分离展现(视图)、数据(模型)和业务逻辑(控制器)。确切地讲,模型是简单数据,如Person或Address类。视图负责在屏幕上呈现数据。在iOS开发中,视图是UIView的一个子类。iOS提供了一个特殊类作为UIView的控制器,该类被贴切地命名为UIViewController。
UIViewController具有两个重要特征:往往与一个XIB文件关联,有一个名为“view”的UIView类型属性。通过创建UIViewController的一个子类,可以得到一个同名XIB文件,可作为视图类使用。默认情况下,当实例化一个UIViewController子类时,会加载一个同名的XIB。XIB中的根UIView将绑定至UIViewController的view属性。
除在UI布局和逻辑驱动之间提供干净的分离外,iOS还提供了大量UIViewController子类,可与其他UIViewController一起使用(而不是UIView)。UINavigationController便是一例,用于实现在设置应用程序中可以看到的导航类型。在代码中,如果你希望前进到下一视图,传递一个UIViewController而不是一个UIView,尽管它是显示在屏幕上的UIViewController的view属性。
不可否认,对于本章的示例应用程序,使用UIViewController并不会产生太大差异。第1章中扩展UIView的同时创建了RockPaperScissorsView类,它工作得很好。然而,理解UIViewController及其视图之间联动工作的原理会使工作变得更加轻松。在第3章中将探讨一个游戏的应用程序的生命周期。
使用第1章中的RockPaperScissorsView,让我们看一下如何以UIViewController的方式来实现该功能。程序清单2-3显示了文件RockPaperScissorsController.h的内容。
程序清单2-3 RockPaperScissorsController.h
@interface RockPaperScissorsController : UIViewController {
UIView* buttonView;
UIButton* rockButton;
UIButton* paperButton;
UIButton* scissersButton;
UIView* resultView;
UILabel* resultLabel;
UIButton* continueButton;
BOOL isSetup;
}
-(void)setup:(CGSize)size;
-(void)userSelected:(id)sender;
-(void)continueGame:(id)sender;
-(NSString*)getLostTo:(NSString*)selection;
-(NSString*)getWonTo:(NSString*)selection;
@end
在程序清单2-3中,我们看到类RockPaperScissorsController扩展自UIViewController。除其他事项外,这意味着RockPaperScissorsController具有一个名为“view”的属性,该属性是控制器的根UIView。与RockPaperScissorsView类似,同样有其他的UIView作为该根视图的子视图,例如供用户选择的按钮。尽管可以想到这些按钮都有各自的UIViewController,但是此时有理由让一个UIViewController来统一管理所有相关的UIView。在实现方面只做很少的修改即可完成从UIView到UIViewController的过渡。基本上,我们只需要把使用关键字“self”的位置替换为“self.view”即可。程序清单2-4中显示了所需的修改。
程序清单2-4 RockPaperScissorsController.m(部分)
-(void)setup:(CGSize)size{
if (!isSetup){
isSetup = true;
srand(time(NULL));
buttonView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,
size.width, size.height)];
[buttonView setBackgroundColor:[UIColor lightGrayColor]];
[self.view addSubview:buttonView];
float sixtyPercent = size.width * .6;
float twentyPercent = size.width * .2;
float twentFivePercent = size.height/4;
float thirtyThreePercent = size.height/3;
rockButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[rockButton setFrame:CGRectMake(twentyPercent, twentFivePercent,
sixtyPercent, 40)];
[rockButton setTitle:@"Rock" forState:UIControlStateNormal];
[rockButton addTarget:self action:@selector(userSelected:)
forControlEvents:UIControlEventTouchUpInside];
paperButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[paperButton setFrame:CGRectMake(twentyPercent, twentFivePercent*2,
sixtyPercent, 40)];
[paperButton setTitle:@"Paper" forState:UIControlStateNormal];
[paperButton addTarget:self action:@selector(userSelected:)
forControlEvents:UIControlEventTouchUpInside];
scissersButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[scissersButton setFrame:CGRectMake(twentyPercent,
twentFivePercent*3, sixtyPercent, 40)];
[scissersButton setTitle:@"Scissers" forState:UIControlStateNormal];
[scissersButton addTarget:self action:@selector(userSelected:)
forControlEvents:UIControlEventTouchUpInside];
[buttonView addSubview:rockButton];
[buttonView addSubview:paperButton];
[buttonView addSubview:scissersButton];
resultView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,
size.width, size.height)];
[resultView setBackgroundColor:[UIColor lightGrayColor]];
resultLabel = [[UILabel new] initWithFrame:CGRectMake(twentyPercent, thirtyThreePercent,
sixtyPercent, 40)];
[resultLabel setAdjustsFontSizeToFitWidth:YES];
[resultView addSubview:resultLabel];
continueButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[continueButton setFrame:CGRectMake(twentyPercent,
thirtyThreePercent*2, sixtyPercent, 40)];
[continueButton setTitle:@"Continue"
forState:UIControlStateNormal];
[continueButton addTarget:self action:@selector(continueGame:)
forControlEvents:UIControlEventTouchUpInside];
[resultView addSubview:continueButton];
}
}
-(void)userSelected:(id)sender{
int result = random()%3;
UIButton* selectedButton = (UIButton*)sender;
NSString* selection = [[selectedButton titleLabel] text];
NSString* resultText;
if (result == 0){//lost
NSString* computerSelection = [self getLostTo:selection];
resultText = [@"Lost, iOS selected " stringByAppendingString:
computerSelection];
} else if (result == 1) {//tie
resultText = [@"Tie, iOS selected " stringByAppendingString:
selection];
} else {//win
NSString* computerSelection = [self getWonTo:selection];
resultText = [@"Won, iOS selected " stringByAppendingString:
computerSelection];
}
[resultLabel setText:resultText];
[buttonView removeFromSuperview];
[self.view addSubview:resultView];
}
-(void)continueGame:(id)sender{
[resultView removeFromSuperview];
[self.view addSubview:buttonView];
}
程序清单2-4中的粗体部分标明了需做的修改。我们将在本章末尾处的石头、剪刀、布示例程序中使用此UIViewController。下面将完成剩余UI部分的设置工作。
基于设备类型自定义行为
如前所述,我们正在使用的项目是一个通用应用程序的示例,被配置为在iPhone和iPad上均可运行。因为很有可能一个应用程序运行于不同设备时的表现会有所不同,所以我们将针对不同设备类型创建自己的ViewController的子类。
要创建这些子类,从File菜单中选择New File,这会弹出一个对话框,如图2-9所示。
从Cocoa Touch部分,我们选择UIViewController subclass,然后点击Next按钮。这将允许我们对新建的类命名,并为其指定一个特定的子类,如图2-10所示。
图2-9 新建文件对话框
图2-10 新建UIViewController类的详细信息
该类的名称应该设置为ViewController_iPhone,它应该是ViewController的一个子类。请记住,ViewController类是一个UIViewController,因此ViewController_iPhone同样是一个UIViewController。在本例中,由于已经有了一个用于该类的XIB文件,因此在对话框中,不需要选中任何一个复选框。重复上述操作,创建一个UIViewController的iPad版本。创建该类时,将其命名为UIViewController_iPad,同样也不要选中任意一个复选框。完成上述操作后,你的项目此时应该如图2-11所示。
图2-11 添加了设备专有的UIViewController子类
在图2-11中,可以看到我们的项目以及刚才新建的UIViewController子类。我发现将设备专有的类放在各自的分组中,会使工作变得更有条理。
现在,我们已经有了对应于每类设备(iPhone和iPad)的XIB文件和UIViewController类。如果我们要编写共享行为的代码,可将这些代码放在ViewController类中。如果我们要针对具体的特定设备编写代码,可将相应代码添加在ViewController_iPad或ViewController_iPhone中。现在可以继续开始着手实现我们的应用程序了。首先看一下UI元素。