本书第10章介绍过,编写应用时,经常需要在视图控制器之间传递数据。为了演示如何在Storyboards中传递数据,本节将在Colorboard中添加一个颜色收藏列表,用于保存BNRColorViewController中用户选择的颜色。
本节中的UITableView对象将不再使用Static Cells,而是使用Dynamic Prototypes(动态原型)。因此,接下来需要为UITableView对象实现一系列数据源方法。如果需要在数据源中返回不同类型的单元格,就可以使用动态原型。只要为不同类型的单元格分配唯一的重用标识,UITableView对象就可以根据数据源方法显示相应的单元格。
在Colorboard.storyboard中,删除红色和绿色的两个视图控制器,然后选中UITableView对象,打开属性检视面板。接下来点击标题为Content的下拉菜单,改为Dynamic Prototypes,最后在画布中删除第二个UITableViewCell对象。这时storyboard看起来应该类似于图28-19。
图28-19 动态原型
下面选中唯一的UITableViewCell对象,在Identifier文本框中输入UITableViewCell(见图28-20)。
图28-20 设置UITableViewCell对象的重用标识
现在需要为Table View Controller创建对应的UITableViewController子类,以便为UITableView对象提供数据。创建一个新的NSObject子类,名为BNRPaletteView- Controller。
打开BNRPaletteViewController.h,将其父类修改为UITableViewController。
@interface BNRPaletteViewController : NSObject
@interface BNRPaletteViewController : UITableViewController
@end
再打开BNRPaletteViewController.m,导入BNRColorViewController.h,然后在类扩展中添加一个NSMutableArray属性。
#import “BNRPaletteViewController.h”
#import “BNRColorViewController.h”?
@interface BNRPaletteViewController ()
@property (nonatomic) NSMutableArray *colors;
@end
@implementation BNRPaletteViewController
接下来实现viewWillAppear:和UITableView对象的数据源方法:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [self.colors count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”
forIndexPath:indexPath];
return cell;
}
创建一个名为BNRColorDescription的NSObject子类,表示用户选择的颜色。
打开BNRColorDescription.h,添加一个UIColor属性和NSString属性,分别表示颜色和颜色名称:
@interface BNRColorDescription : NSObject
@property (nonatomic) UIColor *color;
@property (nonatomic, copy) NSString *name;
@end
再打开BNRColorDescription.m,覆盖init方法,设置两个属性的默认值:
@implementation BNRColorDescription
- (instancetype)init
{
self = [super init];
if (self) {
_color = [UIColor colorWithRed:0
green:0
blue:1
alpha:1];
_name = @“Blue”;
}
return self;
}
@end
为了测试代码是否可以正常工作,下面向BNRPaletteViewController的colors数组中添加一个BNRColorDescription对象。
在BNRPaletteViewController.m中引入BNRColorDescription.h,然后覆盖colors的取方法,添加一个BNRColorDescription对象。
#import “BNRPaletteViewController.h”
#import “BNRColorDescription.h”
@implementation BNRPaletteViewController
- (NSMutableArray *)colors
{
if (!_colors) {
_colors = [NSMutableArray array];
BNRColorDescription *cd = [[BNRColorDescription alloc] init];
[_colors addObject:cd];
}
return _colors;
}
同时,更新数据源方法,显示颜色名称:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”
forIndexPath:indexPath];
BNRColorDescription *color = self.colors[indexPath.row];
cell.textLabel.text = color.name;
return cell;
}
构建并运行应用。现在应用应该可以点击UITableViewCell对象进入BNRColorViewController界面,但是存在两个问题:首先,BNRColorViewController无法显示正确的颜色;其次,BNRColorViewController会同时显示Back(返回)按钮和Done按钮,正确做法是,只有在添加新颜色以模态形式进入BNRColorViewController时才显示Done按钮。为了解决以上两个问题,需要在BNRPaletteViewController和BNRColorViewController之间传递需要的数据,包括BNRColorDescription对象以及该对象是否已经存在。
打开BNRColorViewController.h,在顶部导入BNRColorDescription.h,然后添加两个新属性:第一个用于判断编辑的是新颜色还是已经存在的颜色;第二个用于表示正在编辑的颜色:
#import “BNRColorDescription.h”
@interface BNRColorViewController : UIViewController
@property (nonatomic) BOOL existingColor;
@property (nonatomic) BNRColorDescription *colorDescription;
@end
当UIViewController对象触发UIStoryboardSegue对象时,它会收到prepareForSegue:sender:消息。prepareForSegue:sender:的两个参数分别是UIStoryboardSegue对象和动作控件。UIStoryboardSegue对象包含三个方面的信息:源视图控制器(source view controller)、目标视图控制器(destination view controller)和标识。为了区分不同的UIStoryboardSegue对象,下面为两个UIStoryboardSegue对象设置不同的标识。
重新打开Colorboard.storyboard,选中modal样式的UIStoryboardSegue对象,然后打开属性检视面板,在Identifier文本框中输入NewColor(新颜色);同样,选中push样式的UIStoryboardSegue对象,设置其Identifier为ExistingColor(已经存在的颜色),如图28-21所示。
图28-21 为两个UIStoryboardSegue对象设置标识
现在,两个UIStoryboardSegue对象都具有标识,因此,可以在程序中根据标识判断用户是正在添加新颜色还是选择了已经存在的颜色,并使用不同的方式为BNRColorView- Controller对象传入BNRColorDescription对象。打开BNRPaletteViewController.m,实现prepareForSegue:sender:方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@“NewColor”]) {
// 如果是添加新颜色,
// 就创建一个BNRColorDescription对象并将其添加到colors数组中
BNRColorDescription *color = [[BNRColorDescription alloc] init];
[self.colors addObject:color];
// 通过UIStoryboardSegue对象
// 设置BNRColorViewController对象的颜色(colorDescription属性)
UINavigationController *nc =
(UINavigationController *)segue.destinationViewController;
BNRColorViewController *mvc =
(BNRColorViewController *)[nc topViewController];
mvc.colorDescription = color;
}
else if ([segue.identifier isEqualToString:@“ExistingColor”]) {
// 对于push样式的UIStoryboardSegue对象,sender是UITableViewCell对象
NSIndexPath *ip = [self.tableView indexPathForCell:sender];
BNRColorDescription *color = self.colors[ip.row];
// 设置BNRColorViewController对象的颜色,
// 同时设置其existingColor属性为YES(该颜色已经存在)
BNRColorViewController *cvc =
(BNRColorViewController *)segue.destinationViewController;
cvc.colorDescription = color;
cvc.existingColor = YES;
}
}
在prepareForSegue:sender:方法中,首先检查segue参数的identifier,确定触发的是哪个UIStoryboardSegue对象。如果用户点击了“+”按钮,那么触发的segue是“NewColor”,需要创建一个新的BNRColorDescription对象并将其传给BNRColorView- Controller;如果用户选择了已经存在的颜色,那么触发的segue是“ExistingColor”,需要将用户选择的颜色传给BNRColorViewController(如果UIStoryboardSegue对象的动作控件是UITableViewCell对象,可以通过UITableViewCell对象知道用户选择的是哪个NSIndexPath对象)。
(请注意,“NewColor”的destinationViewController是UINavigationController对象,而“ExistingColor”的destinationViewController是BNRColorViewController对象。打开storyboard文件,可以看到,modal样式的UIStoryboardSegue对象指向一个UINavigationController对象,而push样式的则指向一个BNRColorViewController对象。push样式的UIStoryboardSegue对象直接将BNRColorViewController对象压入已经存在的UINavigationController栈中。)
下面还需要在BNRColorViewController中编写以下代码:如果用户选择的是已经存在的颜色,就不能显示Done按钮;需要根据colorDescription属性设置view的背景颜色、颜色名称和UISlider对象的滑块值;当BNRColorViewController的view从屏幕上消失时,需要保存用户选择的新颜色。
在BNRColorViewController.m中覆盖viewWillAppear:方法,如果颜色已经存在,就移除Done按钮:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 如果颜色已经存在,就移除Done按钮
if (self.existingColor) {
self.navigationItem.rightBarButtonItem = nil;
}
}
接下来覆盖viewDidLoad方法,初始化view的背景颜色、颜色名称和UISlider对象的滑块值。
- (void)viewDidLoad
{
[super viewDidLoad];
UIColor *color = self.colorDescription.color;
// 从UIColor对象中取出RGB颜色分量
float red, green, blue;
[color getRed:&red
green:&green
blue:&blue
alpha:nil];
// 初始化UISlider对象的滑块值
self.redSlider.value = red;
self.greenSlider.value = green;
self.blueSlider.value = blue;
// 初始化view的背景颜色和颜色名称
self.view.backgroundColor = color;
self.textField.text = self.colorDescription.name;
}
最后,当view消失时保存颜色和颜色名称。
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.colorDescription.name = self.textField.text;
self.colorDescription.color = self.view.backgroundColor;
}
构建并运行应用,Colorboard应该可以正确地显示并保存颜色。