1 简介
1.1 故事板简介
如果你的 app 有大量的窗口,故事板能帮你减少许多用于从一个窗口转到另一个窗口的导航代码。与每个viewcontroller一个单独的 nib 文件不同,你的 app 只需用一个故事板文件(其中可以包含所有的viewcontroller 以及它们之间的关系)即可。
与传统的 nib 文件不同,故事板文件有以下优点:
1、通过一个故事板文件,你能对所有的窗口以及它们之间的关系一目了然。因为所有的窗口设计在一个故事板文件中,你会更容易把握每次改动带给每个窗口的变化。
2、在故事板中可以描述各个窗口之间的转换。这种转换叫做 segue(连接)。创建 segue 只需用 ctrl+ 拖拽从一个 viewcontroller 拖到另一个 viewcontroller 即可。这将减少窗体导航的代码。
3、哪怕在 tableview 上故事板仍然有用,例如定制 tableviewcell。你完全可以在故事板编辑器中设计自己的 tableview,这也节省了不少代码。
点击 MainStoryboard.storyboard 文件,将打开故事板编辑器:
故事板编辑器从外表上看很像是IB。你可以从 Object Library中拖控件(右下角)到viewcontroller 中,并修改它的布局。不同的是,故事板中不仅仅包含一个viewcontroller,而是包含 app 中的所有viewcontroller。
故事板有一个专门的术语“场景”,一个“场景”用于表示一个viewcontroller。你以前每个场景/viewcontroller就要用一个单独的 nib 文件,但现在所有的东西都集中到了一个故事板中。
对于 iPhone 应用,一次可以看一个场景,但iPad 应用可以一次显示多个场景,例如使用splitview 的“主-细”窗口,或者用popovercontroller 弹出内容。
1.2 程序加载
如果你以前创建过基于 nib 的app(译者注:Xcode 3.x),你可能知道MainWindow.xib 文件。这个nib 文件中包含了一个顶层的连接到 App Delegate 的UIWindow 对象,以及一个或多个viewcontroller。但是,当你使用故事板的时候,所有的UI 都放到了一个故事板中, MainWindow.xib 不再使用。
那么,在没有 MainWindow.xib 文件的情况下,故事板是怎样被加载到app中的呢?
打开 AppDelegate.h,你将看到这几句:
#import
@interface AppDelegate : UIResponder
@property (strong, nonatomic) UIWindow *window;
@end
当使用故事板的时候,应用程序委托必须从 UIResponder 开始继承(原先则直接从NSObject继承),同时还有一个 UIWindow 属性(不同的是,它不是一个IBOutlet)。
在 AppDelegate.m 中,它实际上什么也没做,所有的方法都是空的。甚至application:didFinishLaunchingWithOptions:也只是简单地返回 Yes。如果是过去,要么要在这里加入主viewcontroller的 view 到 window,要么设置window 的 rootViewController 属性。但现在什么都没有。
秘密都位于 Info.plist 文件。打开Ratings-Info.plist(在Supporting Files 文件组),在 nib-based 的项目中,Info.plist 文件中有一个名为NSMainNibFile 或者 Main nib file base name 的键,它会导致UIApplication去加载 MainWindow.xib 并将之连接到 app 中。现在,Info.plist中不再有这个设置。
与之对应的是,故事板应用程序使用 UIMainStoryboardFile或者“Main storyboard file base name”键。它要求应用程序在启动时需要加载的故事板文件名。当这个键缺失时,UIApplication将默认加载MainStoryboard.storyboard 文件并自动将故事板中第一个viewcontroller 初始化并放到一个新创建的UIWindow 对象中。这一切不再需要手工编写代码。
你可以查看 Target 的 Summary 窗口:
新增的 iPhone/iPodDeployment Info 小节下面,可以让你选择是从故事板文件启动还是从nib文件启动。
为了更清楚一点,可以打开 main.m 查看:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
原先的 UIApplicationMain() 的最后一个参数为nil,但现在是 NSStringFromClass([AppDelegate
class])。
与使用 MainWindow.xib 的时候不同,故事板中不会包含应用程序委托。由于无法从nib中加载应用程序委托,也无法从故事板文件中加载应用程序委托,我们必须告诉UIApplicationMain 应用程序委托类的名称,否则它根本无法找到应用程序委托类。
2 使用
2.1 结合xib使用
思路:
APP跳转流程在故事板中呈现,但是VC的事件处理与详细视图设计放在xib文件中。
具体实现方法:
例如,在故事板中Level1VC中添加一个按钮,然后将level2VC拖入故事板,按住cmd键,鼠标点击按钮并拖入level2VC,在弹出的菜单中选择show事件,即完成了从Level1VC跳转到Level2的操作。
2.2 使用TabBar
2.2.1 TabBarController添加
Ratings 程序有一个 Tabbar,包含了两个viewcontroller。使用故事板创建Tabbar 是小事一碟。
切换到MainStoryboard.storyboard,拖一个 TabBarController 到画布中。你可能得将Xcode窗口最大化,因为TabBarController跟两个viewcontroller 联系在一起,你可能需要更多的空间才能灵活操作。
新的 TabBarController 已经事先配置了两个ViewController,每个Tab 按钮一个。UITabBarController 是一种ViewController 的容器,它包含了多个viewcontroller。其它类似的容器还有NavigationController 和 SplitViewController(后面都会介绍)。iOS5有个更酷的特性是你可以写自己的 ViewController 容器——在本书后续教程中介绍。
TabBarController和所包含的ViewController的包容关系用一个箭头(中间有一个小圆图标)表示。
注意:如果要把TabBarController与其包含的ViewController一起移动,用Cmd+左键将它们全部选中然后移动(选中的场景会有一个浅蓝色的方框框住)。
在第一个 ViewController 中放入一个Label 然后输入文本“FirstTab”。在第2个ViewController中放入一个 Label 并输入文本“Second Tab”。这样我们就能在切换Tab 时区分两个ViewController。
注意:你不能在编辑器的缩放模式下向场景拖放东西,必须首先恢复到普通模式下。
选中 TabBarController并打开属性面板。勾选“ IsInitial View Controller”选项。
在画布中,原来指向最初的 ViewController 的箭头,现在指向了TabBarController。也就是说,程序运行时,UIApplication 会将TabBarController作为应用程序的第一个ViewController。故事板总是以一个 ViewController 作为“initialview controller”,即故事板的入口。
运行程序。现在的程序,可以通过TabBarController 在两个ViewController 中切换。
Xcode 其实有一个专门用于Tabbar 应用程序的模板(叫做 Tabbed Application 模板),当然 我们也可以使用这个模板。但在某些时候我们必须通过手动创建TabbarController,这样就必须知道在不使用模板时应该如何去做。
现在你可以删除项目模板原来创建的那个viewcontroller,我们不再需要它。这样故事板中只会有一个TabbarController和它的两个 viewcontroller。
以这种方式,你可以创建超过 5 个的ViewController 给TabBarController,它将自动在Tabbar 上显示 More... 按钮。
2.2.2 添加TableView Controller
被连接到 TabBarController 的两个场景只是一般的UIViewController。现在我们要其中的第一个替换为UITableViewController。
选中第一个 ViewController,删除它。拖一个TablViewController到画布中。选中 TablViewController,选择菜单“Editor\EmbedIn\Navigation”。
这将导致增加一个 ViewController 到画布中:
当然你也可以直接从 Object Library 中拖一个NavigationController,但 Embed In 命令更简单一些。
由于 NavigationController 也是一种ViewController容器,它和 TableViewController 之间也有一个箭头表示二者关系。在文档树中这些关系显示如图中所示:
注意 TableViewController 上被加入了一个navigationBar。这是故事板编辑器自动放入的,因为这个场景现在将在NavigationController 的 frame 内显示。当然,这并不是真正的UINavigationBar对象,而只是一个模拟的“假”的导航条。
打开 TableViewcontroller 的属性面板,我们可以看到顶部有一个Simulated metrics 小节。
故事板默认使用“Inferred”(依靠推断)设置,意思是该场景如果在NavigationController中显示则会显示导航条,如果在 TabBarController 中显示则会显示TabBar,等等。如果你需要的话也可以改变这些设置,但请明白,这些设置仅仅是帮助你设计你的屏幕,Simulated Metrics 并不会用于运行时,它们仅仅是帮助你进行可视化设计的。
现在将新场景连接到TabBarController 。ctrl+左键,从TabBarController拖一条线到NavigationController.
拖完后将显示弹出菜单,请选择Relationship-viewControllers。这将在这两者间创建新的关系:
TabBarController 现在有两个关系,一个Tab 一个。NavigationController自己有一个关系,连接的是TableViewController。此外还有另外一种箭头,“segue”,我们在后面讲。
创建新连接时,新的 Tab 也同时被加到TabBarController上,名字叫做 “Item”。我想将新的场景放在第一个Tab上,可以用拖拽 Tab 的方式改变它们的顺序。
运行程序,现在第一个 Tab 已经变成了NavigationController。
在我们将实际的功能加入 app 之前,让我们整理一下我们的故事板。我想将第一个tab命名为 Players,第2个 tab 命名为Gestures。你不需要去改变 TabBarController,而是要改变与tab 对应的ViewController。
当你将一个 ViewController 连接到TabBarController时,会在 ViewController 上创建一个TabBarItem 对象。通过 TabBarItem 对象,你可以设置Tab的 Title 和图片。
选择 NavigationController 上的TabBarItem对象,在属性面板,设置它的 Title 为Players。将第二个 ViewController 的TabBarItem 重命名为Gestures。
我们还可以在 Tab 上放入图片。在本教程源代码中有一个文件夹Images。将该文件夹添加到项目中去。在TabBarItem “Guestures”的属性面板,将 Players.png 设为它的image。将TabBarItem “Players”的 image 设置为Players.png。
与之相仿,在 NavigationController 所包含的ViewController上,有一个 NavigationItem 对象,可用于设置导航栏。选择TableViewController 上的NavigationItem,在属性面板中将title 修改为 Players。
当然,你也可以通过简单地双击 NavigationBar 来修改title(注意:你应该双击TableViewController 上的“假”导航条,而不是双击NavigationController 上的真导航条。
运行程序,不需要你编写一行代码,我们定制的 Tab 栏就显示出来了。
2.3 模板cells
2.3.1 模板cells使用
注意到当你加入 tableViewController 后,Xcode会发出警告了吗?
“UnsupportedConfiguration: Prototype table cells must have reuse identifiers”,当加入一个TableViewController到故事板后,Xcode 默认会使用一种 prototype cells 的单元格(模板cells)。但我们并没有配置它,因此会有这个警告。
模板 cells 是一种很酷的故事板特性。它远胜于原来的nib 文件。在以前,如果你要定制表视图单元格,你要么在代码中向cell对象添加自己的 subviews ,要么新建一个 nib 然后从nib 中加载你自己的 cell。但模板 cells 的出现简化了这一切,现在你可以直接在故事板编辑器中设计你自己的表视图单元格。
2.3.2 新建PlayerCell
TableViewController 上自带有一个空白的模板cell。点击这个cell,你可以在属性面板中设置它的样式为Subtitle。这会使 cell 变成包含有两个label 的 cell。如果你曾经自己手动创建过TableViewCell,你应该知道这就是UITableViewCellStyleSubtitle样式。通过模板 cells,你可以创建内置样式的cell,也可以创建完全定制的cell(我们马上就会提到)。
将 Accessory 属性改为Disclosure Indicator 然后将 Reuse Identifier (复用ID) 设置为“PlayerCell”。这样 Xcode 会立即消除警告。所有的模板 cells 仍然是普通的 UITableViewCell 对象,仍然会带有一个复用 ID,Xcode仅仅是提示我们别忘了设置它(至少会让我们注意到这个警告)。
运行程序,什么都没有改变。不要奇怪,我们还没有提供数据源,因此表视图中不会显示任何行。
2.3.3 创建PlayersViewController
加一个新的 File 到项目中。选择UIViewController subclass 模板。将类命名为PlayersViewController ,确保它继承于UITableViewController。不要选择“WithXib...”选项,因为我们在故事版中已经为这个类设计了一个UI。我们不再需要nib!
回到故事版编辑器,选择 TableViewController。在Identity 面板,将它的 Class 设置为PlayersViewController。这一步很重要,因为这会将位于故事版中的一个场景与你自己的 ViewController子类关联起来。千万记得这个步骤,否则你创建类将完全没有用处!
从现在开始,运行程序后故事板中的tableViewController 将变成我们的PlayersViewController 类的一个实例。
在 PlayersViewController.h 文件中加入一个可变数组属性:
#import <UIKit/UIKit.h>
@interface PlayersViewController : UITableViewController
@property (nonatomic, strong) NSMutableArray *players;
@end
这个数组将存储应用程序中的模型数据,即Player (玩家)对象。
2.3.4 创建数据模型Player类
现在创建Player 类。创建一个新的File,使用 Objective-C class 模板。命名为 Player,继承NSObject。
Player.h 文件:
@interface Player : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *game;
@property (nonatomic, assign) int rating;
@end
Player.m 文件
#import "Player.h"
@implementation Player
@synthesize name;
@synthesize game;
@synthesize rating;
@end
这些都毫无出奇之处。Player 是一个简单对象,拥有3个属性:玩家姓名、玩家所玩的游戏、以及等级(1-5星)。
2.3.5 构建测试数据源
我们将在 AppDelegate 中放入一个数组,并在数组中放入一些Player对象进行测试。这个数组将被赋值给 PlayerViewController 的players 属性。
在 AppDelegate.m,加入Player类和PlayersViewController类的导入语句,加入一个实例变量叫做players:
#import "AppDelegate.h"
#import "Player.h"
#import "PlayersViewController.h"
@implementation AppDelegate {
NSMutableArray *players;
}
......
修改didFinishLaunchingWithOptions 方法:
- (BOOL) application: (UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
players = [NSMutableArray arrayWithCapacity: 20];
Player *player = [[Player alloc] init];
player.name = @"Bill Evans";
player.game = @"Tic-Tac-Toe";
player.rating = 4;
[players addObject: player];
player = [[Player alloc] init];
player.name = @"Oscar Peterson";
player.game = @"Spin the Bottle";
player.rating = 5;
[players addObject: player];
player = [[Player alloc] init];
player.name = @"Dave Brubeck";
player.game = @"Texas Hold’em Poker";
player.rating = 2;
[players addObject: player];
UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController;
UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex: 0];
PlayersViewController *playersViewController = [[navigationController viewControllers] objectAtIndex: 0];
playersViewController.players = players;
return YES;
}
首先创建了一些 Player 对象并加到 players 数组里。然后:
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex: 0];
PlayersViewController *playersViewController = [[navigationController viewControllers] objectAtIndex: 0];
playersViewController.players = players;
呀,这是什么?我们想将 players 数组赋给PlayersViewController的 players 属性,以便作为TabeViewController 的数据源。但是应用程序委托不知道PlayersViewController在哪里,因此我们不得不把它从故事板中找出来。这是使用故事板的一个令我烦心不已的不足。如果是使用IB ,在 MainWindow.xib中会有应用程序委托的一个引用,同时你可以将顶层的ViewController 连接到应用程序委托的IBOutlet 属性。但现在使用故事板就不可能了。在顶层ViewController 中不能再引用应用程序委托。这真是个不幸,我们只能通过代码方式获得引用。
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
我们知道故事板的 initial view controller 是一个TabBarController,所以我们可以从 window 对象的 rootViewController 获得它的一个引用并进行类型转换。
PlayersViewController 位于第一个tab 的NavigationController 容器中,因此我们先获得UINavigationController 对象:
UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex: 0];
然后在 NavigationController 的rootViewController,可以获得PlayersViewController :
PlayersViewController *playersViewController = [[navigationController viewControllers] objectAtIndex: 0];
但是,UINavigationController 没有 rootViewController属性。因此我们必须从viewControllers 数组中检索。(它有一个 topViewController 属性,但那个是位于viewControllers栈顶的 view controller。而我们要的是栈低的 view controller。虽然在程序刚启动的时候,栈顶和栈底实际上是一个,你也可以使用topViewController,但这不是那么安全)
现在我们有了 Player 数组,可以回到PlayersViewController中创建我们的数据源了。
2.3.6 填充table view视图数据
打开PlayersViewController.m,修改table view 的数据源方法:
- (NSInteger) numberOfSectionsInTableView: (UITableView *)tableView {
return 1;
}
- (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
return [self.players count];
}
重要的是cellForRowAtIndexPath方法。Xcode 创建的模板代码是这样的:
- (UITableViewCell *) tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
staticNSString*CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: CellIdentifier];
}
// Configure the cell...
return cell;
}
毫无疑问,你曾经无数次地在这个地方编写自己的 table view 代码。但现在不同了。将代码修改为:
- (UITableViewCell *) tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: @"PlayerCell"];
Player *player = [self.players objectAtIndex: indexPath.row];
cell.textLabel.text =player.name;
cell.detailTextLabel.text =player.game;
return cell;
}
代码变得更简单了! 其实你只需要从这里获得新的cell :
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: @"PlayerCell"];
不再需要复用单元格了,它会自动从模板 cell 获得一份拷贝给你使用!你只需要提供复用的ID(你曾经在故事版编辑器中为模板cell设置过的,在本例中,即“PlayerCell”)。记得设置这个ID,否则模板cell 不会生效。
由于 PlayersViewController 不认识Player 类,你还需要导入Player 类的头文件:
#import "Player.h"
此外还要合成 players 属性:
@synthesize players;
运行程序,如下图所示:
注意:在本例中,我们只用了一种模板 cell,如果你需要显示多种cell,你可以加入更多的模板cell。你可以复制已有的模板cell为新的cell,也可以增加TableView的 Prototype Cells 属性值。注意,确保每个模板 cell 都有自己的复用ID。
使用神奇的模板cell只需一行代码,这是件了不起的事情!
2.3.7 设计完全自定义的模板cell
对于大部分 app,使用标准的cell 样式就足矣。但我想在单元格右边加一张图片以显示玩家级别(以星级的形式)。UITableViewCell的标准样式中不包含可以在单元格中放入一个ImageView,因此我只能选择定制设计。
回到MainStoryboard.storyboard,选择模板cell,将Style属性设置为 Custom。默认的 label 将消失。
首先增加 cell 的高度为55 像素。拖拽它下端的拉柄可以改变它的高度,也可以修改Size 面板中的Row height 值。
拖两个 Label 到Cell 中,将它们放置到大致等于原先所在的位置。随意修改它们的字体和颜色。将两个label的高亮色为白色。这样当用户点击 cell 时看起来会好一些,因为此时cell的背景为蓝色。
拖一个 ImageView 到cell 右端,紧靠着右箭头。调整它宽度为81,高度无所谓。设置它的Mode 为 Center(在属性面板的 View 下面)以便当我们将图片放入时它不会被拉伸。
我将俩个 label 的宽度设置为210,这样不会遮住ImageView。最终设计完成是这个样子:
由于是定制单元格,我们不再使用cell 的 textLabel 和detailTextLabel 属性来显示文本。这两个标签的属性在我们的cell 中也不再存在。
我们将通过 tag 检索我们想要的 Label。对于 Name 标签,tag设置为100,对于 Game 标签,tag设置为102。你可以在属性面板中设置tag。
打开PlayersViewController.m ,将cellForRowAtIndexPath方法修改为:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: @"PlayerCell"];
Player *player = [self.players objectAtIndex: indexPath.row]; UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
nameLabel.text =player.name;
UILabel *gameLabel = (UILabel *)[cell viewWithTag:101];
gameLabel.text =player.name;
UIImageView * ratingImageView = (UIImageView *)[cell viewWithTag: 102];
ratingImageView.image = [self imageForRating: player.rating];
return cell;
}
这里调用了一个新方法imageForRating,这个方法实现如下:
- (UIImage *)imageForRating: (int)rating {
switch (rating) {
case 1: return [UIImage imageNamed: @"1StarSmall.png"];
case 2: return [UIImage imageNamed: @"2StarsSmall.png"];
case 3: return [UIImage imageNamed: @"3StarsSmall.png"];
case 4: return [UIImage imageNamed: @"4StarsSmall.png"];
case 5: return [UIImage imageNamed: @"5StarsSmall.png"];
}
return nil;
}
再次运行程序。
啊哈,看起来有点不太对劲。我们修改了模板cell 的高度,但tableView 并不知道。有两个办法:改变table view 的 Row Height 属性,或者修改 heightForRowAtIndexPath 方法。前者更为简单,因此我使用了前者。
注意:如果你事先无法确定 cell 高度,或者你有不同高度的几种 cell,你应该使用heightForRowAtIndexPath。
返回MainStoryboard.storyboard,在TableView的 Size 面板中,将 Row Height 设置为55。
如果你用拖拽而不是直接键入的方式改变cell 的高度,tableview 的 Row Height 属性也会自动随之改变。
再次运行程序,这次看起来就好多了。
2.3.8 子类化模板Cell
我们的 Table View 看起来不错吧!但我并不喜欢用tag 去访问 UILabel 和其他 cell 的 subview。如果这些Label 能连接到IBOutlet 属性岂不是更好?
在项目中添加新的 File,使用Objective-C class 模板。类名为PlayerCell ,继承自UITableViewCell。
修改 PlayerCell.h 为:
@interface PlayerCell :UITableViewCell
@property (nonatomic, strong) IBOutlet UILabel *nameLabel;
@property (nonatomic, strong) IBOutlet UILabel *gameLabel;
@property (nonatomic, strong) IBOutlet UIImageView *ratingImageView;
@end
修改 PlayerCell.m 为:
#import "PlayerCell.h"
@implementationPlayerCell
@synthesize nameLabel;
@synthesize gameLabel;
@synthesize ratingImageView;
@end
内容不多,仅仅是加了几个属性,nameLabel, gameLabel 以及 ratingImageView。
回到MainStoryboard.storyboard,选择模板cell ,在 Identity 面板改变其 Class 为“PlayerCell”。这样当你用dequeueReusableCellWithIdentifier 方法获得一个 cell时,它实际上返回一个PlayerCell给你。
注意,我将类的名字和重用 ID 取成了一样——都叫做 PlayerCell——这仅仅是因为我喜欢这样。其实二者毫无干系,你完全让它们不一样。
选择,你可以将 label 和ImageView 连接到IBOutlet。选中Label 然后从它的连接面板拖一条线到TableViewCell,或者用 Ctrl+左键从TableViewCell 拖到 Label 上。
重点:你可以在控件和 TableViewCell 间建立连接,而不仅仅是在控件和 ViewController 间建立连接!如你所见,当你的数据源用 dequeueReusableCellWithIdentifier向 Table View 请求新的单元格时,TableView并不真正把模板 cell 给你,它只是给你一份模板 cell 的拷贝(也可能是一个已经存在的cell——在复用的情况下)。也就是说任何时候都存在多个 PlayerCell 实例。如果你连接 cell 上的一个Label 到ViewController 的 IBOutlet上,那么会有多个Label 在试图使用相同的 IBOutlet。那就麻烦了。(顺便说一句,如果在你的Cell上有一个 Custom Button 或者其他控件,你可以将模板cell 连接到 ViewController 的action 上。
现在,我们已经连接好这些属性。我们的代码可以变得更加简洁:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
PlayerCell *cell = (PlayerCell *)[tableView dequeueReusableCellWithIdentifier: @"PlayerCell"];
Player *player = [self.players objectAtIndex: indexPath.row];
cell.nameLabel.text =player.name;
cell.gameLabel.text =player.game;
cell.ratingImageView.image= [self imageForRating: player.rating];
return cell;
}
这样还差不多。我们将dequeueReusableCellWithIdentifier返回的结果转换为PlayerCell,然后用它的属性去访问Label 和 UIImageView。我真的喜欢使用模板cell,它使我的TableView 代码看起来整洁多了。
当然,你仍然需要导入 PlayerCell 类:
#import "PlayerCell.h"
运行程序,跟前面一模一样,但在表格中使用的是我们自己的TableViewCell 子类。
还有一些设计技巧。在设计自己的TableViewCell 时,你需要注意一些地方。首先,你应当设置Label 的 Highlighted Color(高亮色) ,以便用户在点击表格行时感觉更好。
其次,你应当确保添加的内容能自动适应单元格尺寸的变化。例如,当你需要表格行能够被删除或移动时 ,Cell 尺寸会发生改变。
添加下列方法到 PlayerViewController.m:
- (void)tableView: (UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath: (NSIndexPath*)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.players removeObjectAtIndex: indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject: indexPath] withRowAnimation: UITableViewRowAnimationFade];
}
}
实现这个方法后,表格的“轻扫以删除”功能被开启。运行程序,在某行上进行轻扫手势,看看会发生什么。
删除按钮出现在 cell 上,但它同时也遮住了等级图片。实际上是因为删除按钮占据了部分cell空间,而 cell 大小随之改变,ImageView 却没有改变。
要解决这个问题,打开 MainStoryBoard.storyboard,选择 ImageView ,在 Size 面板中修改 Autosizing 以便它始终位于 superview 的右端:
Label 的 Autosizing 设置如下,因此当 cell 尺寸改变时,Label 的尺寸也随之变化:
经过这些调整,删除按钮的出现会将星级图标挤到左边:
你也可以在删除按钮出现时让星星们消失,这就留给读者们自己去实现了。重要的是,你应该在设计TableViewCell 时对这些细节性的东西一清二楚。
3 设计原则
3.1 布局设计原则
3.1.1 一个控件的布局尽量只采用一种方式,要么是Storyboard(XIB)要么是代码
因为视图在刷新时,会直接从Storyboard中加载控件的大小,而不是代码!!!
3.1.2 不能同时设置一个控件横向或纵向的相对间距后,又去设置绝对尺寸,否则会导致控件不能显示,也不会报错!
3.1.3 在故事板中进行布局设计时,如果有导航栏、Tab栏,必须也要把高度预留出来
iPhone iPad各种控件默认高度
http://blog.csdn.net/chengyakun11/article/details/7565690
3.1.4 一个VC继承自另一个VC,对于父类的View,如果子类初始化时想不一样大小,如何在故事板中处理
4 开发技巧
4.1 View分辨率
4.1.1 wAny和hAny——为什么Xcode6的故事板分辨率是480x480
那只是设计时提供给你的一个默认平台,并且可以适应各种不同大小分辨率。你也可以通过下面的 wAny 和hAny 调整它的大小,也可以通过调整 Attributes inspector 指定为具体某种屏幕的大小。通过 AutoLayout 可以实现运行时根据设备实际屏幕大小调整控件位置和大小。
4.2 代码实例化故事板中的VC
如何装载Storyboard中的ViewController?
http://blog.csdn.net/ztp800201/article/details/8987005
UIStoryboard* storyboard = [UIStoryboard storyboardWithName: @"Main" bundle: nil];
HJSoreMapViewController* mapVC = (HJSoreMapViewController*)[storyboard instantiateViewControllerWithIdentifier:@"HJSoreMapViewController"];
Objc代码
// 从storyboard创建MainViewController
UIStoryboard* storyboard = [UIStoryboard storyboardWithName: @"nailshop" bundle:[NSBundle mainBundle]];
YLSMainViewController *mainViewController = (YLSMainViewController*)[storyboard instantiateViewControllerWithIdentifier: @"mainViewController"];
[self presentViewController: mainViewController animated:YES completion: nil];
在调用之前,需要在storyboard里,给目标ViewController设置identifier。
5 参考链接
IOS编程教程(十):使用StoryBoard来建立导航控制器和表视图
http://www.cnblogs.com/haichao/archive/2012/11/23/2784144.html
iOS 5故事板入门(1)
http://blog.csdn.net/kmyhy/article/details/11472777
iOS Storyboard全解析
http://www.cnblogs.com/jy578154186/archive/2013/02/27/2934853.html
使用storyboard实现页面跳转,简单的数据传递
http://blog.csdn.net/mad1989/article/details/7919504
如何装载Storyboard中的ViewController?
http://blog.csdn.net/ztp800201/article/details/8987005
用代码创建并实例化在storyboard中声明的ViewController