感谢翻译小组成员heartasice热心翻译。如果您有不错的原创或译文,欢迎提交给我们,更欢迎其他朋友加入我们的翻译小组(联系qq:2408167315)。
==========================================================================================
Storyboard是一项令人兴奋的功能,在iOS5中首次推出,在开发app的界面时可以极大地节省时间。
如下图所示,这就是一个完整的应用的storyboard,接下来我们要学习如何通过这种方式创建应用。
现在你可能还不是很精确地知道我们的应用可以做什么,但是通过上图,我们可以很清晰的明白这些视图之间的关系。这就是使用storyboard的强大之处。
当你的应用有许多不同页面的时候,通过使用storyboard,可以大大减少页面之间跳转的代码。过去我们为每个视图控制器创建一个nib文件,现在,只需要使用一个storyboard,它包含了你应用中所有的视图控制器以及它们之间的关系。
相比传统的nib文件,storyboard有很多优点:
1.使用storyboard,可以更好地理解应用中所有视图在概念上的概览以及它们之间的关系。掌控所有的视图变得很容易,因为所有的设计都是在一个文件中,而不是在很多单独的nib文件中。
2.storyboard描述了视图之间的动画,这些动画叫做"segues"你可以很容易的通过从一个视图控制器(点ctrl-dragging)拖拽到另一个来实现,感谢"segues"让我们不需要写代码去控制页面跳转了。
3.storyboard通过新的cell原型,以及静态cell的特性,让表格控制器实现起来更容易了。你近乎可以完全的通过storyboard来设计你的表格控制器,这也大大的减少了你不得不写的代码量。.
并不是所有的都是那么完美,当然使用storyboard也是有一些限制的,storyboard的Interface builder 远没有旧版nib编辑器那么强大,并且有一些东西只能在nib中做而不能在storyboard编辑器中做。还有,你需要一个大号的显示器,尤其是在开发iPad应用的时候。
如果你是那种不喜欢用Interface builder 画界面而是用代码直接写界面的人,很遗憾,storyboard并不适合你。就个人而言,我比较倾向于写的代码越少越好,特别是控制UI的那种,所以storyboard简直就是为我准备的一把利器。
当然了,如果你想继续使用nib,那么继续用好了,你需要知道的是你可以通过storyboard来绑定各视图控制器,对于使用storyboard,还是旧版的nib,不是一个非做选择不可的事,随你好了。
在这个例子中,我们将会学到--通过storyboard我们可以做什么,我们将会创建一个简单的应用,功能大致是有一个现实玩家,游戏,以及玩家技能评分的这么一个列表。在学习过程中,你几乎可以学到大多数通过故事板可以做到的事。
storyboard教程 : iOS 7 版
打开Xcode创建一个新项目,这里我们用 Single View Application 模板:
按照下面的方式填写模板中的选项:
1.Product Name: Ratings
2.Organization Name: 随便写点什么都行
3.Company Identifier:域名反过来写,例如com.xx.xx
4.Class Prefix: 空着就行
5.Devices: iPhone
用旧版Xcode的时候你需要特别指定下这个新项目是用storyboard的,但XCode5之后,不再有这个选项了,默认就是用storyboard创建的项目。
在Xcode创建完项目之后,Xcode的主界面上应该如下图所示:
这个新项目包含2个类:AppDelegate 和 ViewController, 以及我们这个例子中的明星: Main.storyboard 文件. 请注意,这个项目中并没有xib文件。
这是一个只支持竖屏的应用,所以在继续之前, 钩掉 Deployment Info, Device Orientation下面的 Landscape Left和Landscape Right 选项。
接下来让我们看一下storyboard,点击列表中的 Main.storyboard 在interface builder中打开它。
在Interface builder中编辑storyboard就跟编辑nib文件差不多,你可以从Object Library中拖拽新的元素到视图控制器中,并且可以编辑它的布局。区别在于storyboard不仅仅有一个视图控制器,它把你应用中的所有视图控制器全都包含了。
按标准的storyboard的说法,一个视图控制器就是一个"场景"。你可以交替的使用这个模式,"场景"是呈现在storyboard中的视图控制器,以往,你可能为每一个"场景"/视图控制器创建一个nib文件。现在你只需要把他们都集中在一个storyboard里。在iPhone中,一次只能看到一个场景,而iPad应用中,一次可能会看到多个。例如"master/detail "或者 "popover"
注意: Xcode 5默认storyboard以及nib中的 Auto Layout属性是打开的。 Auto Layout 是一个新的很帅气的可以自动调整控件大小的这么一个技术,在应用适配iPad以及iPhone5的时候尤其有用,可惜它只能在 iOS 6 以上运行。当然了,它也需要一定的学习曲线,这个例子中我们不会详细的说这点。如果你有兴趣了解 Auto Layout,请参阅我们的书籍《 iOS 6 by Tutorials andiOS 7 by Tutorials》.
在storyboard中的File inspector 禁用Auto Layout选项,如下图:
拖拽一些控件到空的视图控制器上,感受一些storyboard编辑器是如何工作的。
在storyboard编辑页面中找到下面这个标上红色左箭头的按钮
点击它打开左侧的 Document Outline 视图
当编辑nib的时候,列表中显示的是这个nib中所有的控件,但对于storyboard,它会显示你的应用中的所有视图控制器的内容。上图只有一个视图控制器,在接下来的例子中,我们会增加其他的。
在scene下面有微型版的Document Outline,叫做Dock, 如下图:
什么是 Dock ?
它显示了当前视图的最上层对象,每个视图都至少有一个View Controller 对象,一个First Responder 对象,一个Exit 项目。当然了,它也可能会有其他的最上层对象。使用Dock 去连接outlets 和 actions变的非常容易,当你想把某个对象连接到视图控制器中时,只需简单地把它的图标拖拽到Dock上。
注意: 你很可能没怎么用过First Responder。这是指任何物体在任何给定时间具有第一响应状态的代理对象。它一直在你的nib中,你很可能从来没有必要使用它。举个例子,按钮中有个Touch Up Inside事件,把它拖给First Responder的 cut: selector。如果在某一点的文本字段具有输入焦点,那么你可以按下该按钮,使文字栏位,也就是现在的第一响应者,切其文本到剪贴板。
运行该app,它看起来应该和你在编辑器中设计的一样(此处使用了运行iOS 7的4-inch Retina iPhone simulator):
如果你写过以nib为基础的应用,通常会有一个叫做MainWindow.xib的文件。这个文件有一个UIWindow对象指向App Delegate,以及其他视图控制器。当你想用storyboard做这些的时候,就不需要MainWindow.xib了。那么storyboard是如何加载到应用中的呢?
让我们看看应用的 application delegate。 打开 AppDelegate.h,看起来应该是这样:
- #import <UIKit/UIKit.h>
- @interface AppDelegate : UIResponder <UIApplicationDelegate>
- @property (strong, nonatomic) UIWindow *window;
- @end
使用storyboard,应用代理必须继承自UIResponder,并且有一个 UIWindows 属性。下面打开AppDelegate.m,这里设么都没有,所有的方法都是空的,甚至application:didFinishLaunchingWithOptions: 也仅仅是返回了一个YES。
秘密藏在Ratings-Info.plist 里,让我们在Supporting Files group 里找到并打开它,如下图:
storyboard使用UIMainStoryboardFile 或者"Main storyboard file base name" 来指定当应用启动的时候,哪一个storyboard是必须被加载的。当设置生效,UIApplication会自动加载叫这个被命名的storyboard文件,并把它第一个视图控制器显示到UIWindow中。这些都不必写代码去实现。这些你还可以通过修改 Project Settings下面的Project Settings来实现,如下图:
出于完整性, 打开 main.m 如下图:
- #import <UIKit/UIKit.h>
- #import "AppDelegate.h"
- int main(int argc, char *argv[])
- {
- @autoreleasepool {
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
- }
- }
app delegate 并不是storyboard的一部分,我们需要把它的名字指定给UIApplicationMain() 否则,应用就会找不到它。
把它加到标签上
这个Ratings 应用是由标签控制的2个视图 ,使用storyboard,创建标签很容易。
回到Main.storyboard, 把一个Tab Bar Controller 从Object Library 拖到面板中。你可能需要最大化你的XCode,因为Tab Bar Controller 默认会带 2个视图控制器,你需要能完整的看到他们,通过右下角的小浮动窗口,可以缩小这个面板。
一个新的Tab Bar Controller 默认会带2个视图管理器,一个标签一个。UITabBarController也被叫做容器视图控制器,因为一般它都会包含一个以上的视图控制器。此外还有其他两种容器控制器:Navigation Controller和the Split View Controller。
容器控制器通过箭头管理它的视图,如下图
注意: 如果你想把Tab Bar Controller 的所有视图一起移动,你需要点?-click选中这些视图,然后再移动他们(被选中的视图会有一个蓝色的下划线)。
在第一个视图控制器中拖入一个label并且设置它的text 为 "First Tab",同理,在第二个视图管理器中加入"Second Tab" 这可以让你看到标签切换后会发生什么情况。
注意: 在编辑器缩小的时候你不能向面板中拖入控件,你要先恢复到正常缩放水平,双击当前面板就可以做到。
选中Tab Bar Controller 看下面板中的 Attributes inspector。选中那一栏指的是 Initial View Controller。如下图:
在画布上,先由一个箭头指向Tab bar controller
这意味着启动app时, UIApplication 会把Tabbarcontroller 作为主控制器。storyboard同伴有一个独立的视图控制器,他是特定的" initial view controller", 它是storyboard的切入点。
提示: 改变 initial view controller ,你可以通过拖拽在视图控制器之间的箭头。
运行应用试试吧,现在应用的效果是有一个tab bar 并且可以通过tab 去切换视图。如下图:
Xcode 可以创建一个以tab为模板的项目(Tabbed Application template) ,你以前应该用过, 最好理解下它是如何工作的,如果非手动创建,也不至于手足无措。
注意: 当你想Tabbarcontroller 中加入超过5个视图的时候,它会自动的生产一个 More的tab。
删除模板自带的那个视图控制器,现在storyboard中只有一个tab bar 以及两个tab各自对应的一个视图。
添加一个Table View Controller
两个scene隶属的Tab Bar Controller都是标准的 UIViewControllers,下面我们用UITableViewController 替换掉 第一个tab 中的scene。
点击并选中第一个视图控制器,然后删除它,从Object Library中拖拽一个新的 Table View Controller 到先前被删除的scene所处的位置。如下图:
当Table View Controller选中时,在Xcode菜单栏选择 Editor\Embed In\Navigation Controller ,现在另一个视图控制器被加到画板中了:
你还需要从Object Library中拖一个Navigation Controller,但这种嵌入在命令行中一样简单。
因为Navigation Controller 也是一个容器视图控制器(跟Tab Bar Controller一样), 它有一个关系箭头指向Table View Controller,你还可以在 Document Outline看到这个关系:
注意:嵌入Table View Controller给了它一个navigation bar,Interface Builder 自动加上它的,因为当前视图会显示在Navigation Controller的frame里。它并不是一个真的UINavigationBar ,只是一个模拟的。
打开Table View Controller的 Attributes inspector, 你会看到上方有 Simulated Metrics 选项,如下图:
"Inferred" 作为storyboards的默认值,它意味着视图将会显示一个navigation bar,当它处于Navigation Controller中时,显示一个tab bar 当它处于Tab Bar Controller中时。你可以修改这些默认设置,但是注意,这里只是方便你设计用的,"Simulated Metrics"并不会在运行时出现,他们是指一个虚拟的设计,用来展示你的视图最终会是什么样子。
把这两个视图连接到Tab Bar Controller,按Ctrl-drag 从 the Tab Bar Controller 到 the Navigation Controller如下图:
松手的时候, 出现一个弹出窗口如下图:
选择 Relationship Segue - view controllers,它为2个视图创建了一个关系箭头如下图:
Tab Bar Controller有2个关系, 给每个tab都有一个。Navigation Controller自己有一个关系指向Table View Controller。还有另一种类型的箭头"segue" 稍后我们会讲到它。
创建完这个新的连接后,一个新的tab 添加到 Tab Bar Controller了, 简单的命名为 "Item"。对于这个应用,你希望这个新视图作为第一个tab,所以拖动tabs,改变他们的顺序如下图:
运行应用,第一个tab显示了一个包含在navagationcontroller 下面的 table view,如下图:
在你为应用添加具体的功能之前,让我们先整理下storyboard,分别把第一个tab 第二个tab 命名为 "Players" 和"Gestures"。并不像你期望的那样,你不能在Tab Bar Controller 上改动,只可以在tab 指向的那个视图控制器上做。
一旦你把一个视图控制器连上了Tab Bar Controller,它默认会给一个 Tab Bar Item 对象。用这个Tab Bar Item 去改变tab的名字跟图片。
选中Navigation Controller里的Tab Bar Item ,在Attributes inspector中把title 设置为 Players,如下图:
同理把第二个tab 设置为 Gestures.
一个设计良好的应用需要在tab 上有图标。 这里例子代码中 有个Images 的文件夹在 resource 文件夹下 把这个文件夹加入到项目中 ,选中Players Tab Bar的 Item的 Attributes inspector ,把 Players.png 填进去. 同理处理 Gestures item the 图片为 Gestures.png.
一个嵌入在Navigation Controller 的视图控制器有一个 Navigation Item,它是用来配制navigation bar的。在Table View Controller 中选中Navigation Item (在Document Outline可以找到)在 Attributes inspector 中把title改成 Players.
此外, 你可以双击navigation bar 直接修改title. (注意: 你需要双击table view controller 中的simulated navigation bar,并不是Navigation Controller 中的真正的那个Navigation Bar 对象),如下图
运行应用,看看这令人惊叹的tab bar吧,这一切,一行代码都不用写喔~
Prototype cells
Prototype cells 允许你在storyboard编辑器中直接为table view 设计自定义cell。
默认Table View Controller 会带一个空的cell. 点击它,设置 Attributes inspector 中的 Style 为 Subtitle. 这会让你的cell 包含2个 labels.如果你以前为table view 做过自定义cell,你可能注意到,这就是UITableViewCellStyleSubtitle 风格。通过 prototype cells你可以像刚刚那样直接选择自带的cell 风格,或者纯粹自己做一个设计。
设置 Accessory attribute 为 Disclosure Indicator 在 Identifier 中输入 PlayerCell. 所有的 prototype cells 仍然是 UITableViewCell 类型 所以它们需要一个reuse identifier,如下图:
运行应用…什么都没变?这并没有什么值得奇怪的,你还需要为 这个tableview 指定一个data source,这样它才会知道显示什么。
添加一个新文件,选择 Objective-C class 模板,命名为 PlayersViewController ,并且确保它是UITableViewController 的子类。不要选中With XIB for user interface 选项,因为你已经在storyboard中设计好它了,如下图:
回到storyboard选择Table View Controller (确保你只选择了 tableviewcontroller 并没有其他的view 包含在内)。在 Identity inspector中设置它的 Class 为 PlayersViewController. 这对于在storyboard中使用你自己定义的 视图控制器的类很重要,因为如果你不这么做,你的类永远都不会被使用!如下图:
到目前为止 当你运行应用的时候,你的tableviewcontroller 是从storyboard中加载的你的PlayersViewController 的实例。
添加一个 mutable array property 到 PlayersViewController.h:
- #import <UIKit/UIKit.h>
- @interface PlayersViewController : UITableViewController
- @property (nonatomic, strong) NSMutableArray *players;
- @end
该数组将会包含你的应用中的主要数据模型,一个数组包含 Player 对象。使用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 是一个简单的容器对象包含三个属性:玩家名字,游戏名字,他游戏的评级(1-5星)。
在App Delegate 中你需要为这个数组创建一些测试的Player 对象,并且把它分配给PlayersViewController的 players 属性。
在 AppDelegate.m中,在文件顶部为Player和PlayersViewController添加#import ,并且添加一个实例变量 _players ,如下图:
- #import "AppDelegate.h"
- #import "Player.h"
- #import "PlayersViewController.h"
- @implementation AppDelegate
- {
- NSMutableArray *_players;
- }
- // Rest of file...
修改 application: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][0];
- PlayersViewController *playersViewController = [navigationController viewControllers][0];
- playersViewController.players = _players;
- return YES;
- }
我们创建了一些 Player 对象并且把它们加到了 _players 数组中,接下来:
- UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
- UINavigationController *navigationController = [tabBarController viewControllers][0];
- PlayersViewController *playersViewController = [navigationController viewControllers][0];
- playersViewController.players = _players;
嗯?! 我们想把 _players array 分配给PlayersViewController 的 players property,那样它才能把这个数组作为它的data source,但是 app delegate并不知道 PlayersViewController是什么玩意,它会通过storyboard找到它。
注意:这是storyboard的一个限制。用nib的时候你需要在App Delegate 中指定MainWindow.xib 的引用,你可以通过连接顶层的视图控制器到你的App Delegate中。在storyboard里,这是不可能的,你不能在app delegate中为你的顶层视图控制器做引用,这很不幸,所以我们一般都手动的写,就像你现在做的这样。
让我们一步一步来
- UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
你知道 storyboard最初的view controller 是一个Tab Bar Controller,所以你可以指定window的rootViewController 为 UITabBarController.
在第一个tab中,PlayersViewController位于navigation controller,所以你首先要查询UINavigationController 对象,代码如下:
- UINavigationController *navigationController = [tabBarController viewControllers][0];
然后获取它的root view controller, 这里是PlayersViewController:
- PlayersViewController *playersViewController = [navigationController viewControllers][0];
为storyboard制作你想要的视图控制器看来是需要花一些时间的,当然了,如果你想换tab的顺序,或者当前应用不是以tab bar controller 作为 rootviewcontroller了,你将不得不修改这个逻辑。
现在我们有了一个包含Player对象的数组了,我们可以为PlayersViewController创建datasource了。打开PlayersViewController,在顶部添加import:
- #import "Player.h"
修改 table view data source 方法如下
- - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- {
- return 1;
- }
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return [self.players count];
- }
真正的操作发生在cellForRowAtIndexPath 中,起初方法如下:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
- reuseIdentifier:CellIdentifier];
- }
- // Configure the cell...
- return cell;
- }
你需要问你的table view 列出一个cell,如果它返回nil 则说明当前并无可重用的cell,所以你需要自己创建一个新的cell类实例,毫无疑问这需要你手写代码了,用以下方法代替:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
- Player *player = (self.players)[indexPath.row];
- cell.textLabel.text = player.name;
- cell.detailTextLabel.text = player.game;
- return cell;
- }
这看起来很简单,你只需要像下面获取cell
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
如果当前没有一个可以重用的cell 它会自动返回一个prototype cell 的copy 并且返给你。下面你就需要使用在storyboard编辑器中设置的re-use identifier,这里是"PlayerCell"
运行应用,table view如下图所示:
仅仅需要一行代码就可以实现这个新奇的prototype cells。
注意:这个应用我们只用到了一种prototype的cell,如果你想用更多种类的cell,只需要简单地把那些额外的prototype cell 加到storyboard上,你也可以复制当前存在的cell 去做一个新的,或者增加当前table view 的Prototype cell的属性,但是要确保一点,每个cell都需要有一个只属于它自己的re-use identifier
设计你自己的Prototype Cells
很多app 都是使用 默认的cell style的,但是对于这个应用,cell 的右侧有一个图片用来显示这个用户的得分(从1星到5星),默认的cell不带这种具有image的style,所以这里我们需要自己设计一个。
回到Main.storyboard,选择table view 中的prototype cell并把它的Style attribute 设置为Custom,默认的labels 现在没有了。
第一步把cell 稍微加高一点,通过拖拽它的下边,或者改变Size inspector中的Row Height值都可以。 把cell的高度设置为55 points。从Objects Library 拽2个 Label 到cell 上, 把他们放在刚才那个label 所在的地方,随便改改颜色 字图或者写点什么都行。
拖一个Image View 到cell 上,并让它居于cell的右侧,紧挨着disclosure indicator 设置它的Mode 为 Center (在Attributes inspector的view 下面),这样无论你防止什么样的图片到这个Imageview 上它都不会被拉伸。
设置 labels 的宽度为190 points 这样他们就不会挡住image view。
最终的效果如下图所示:
因为这是一个自定义cell所以你再也不需要 UITableViewCell 中的textLabel和detailTextLabel 属性来设置text。因为这些属性指向的 labels 在这个cell中并不存在了。作为替代,你需要使用tags 来找到这些labels。
给Name label 的tag 设置为100,Game 的label 设置为101,Image View 的tag设置为102,通过Attributes inspector 可以找到。
下面打开PlayersViewController.m 增加一个方法,imageForRating:.
如下:
- - (UIImage *)imageForRating:(int)rating
- {
- switch (rating) {
- case 1: return [UIImage imageNamed:@"1StarSmall"];
- case 2: return [UIImage imageNamed:@"2StarsSmall"];
- case 3: return [UIImage imageNamed:@"3StarsSmall"];
- case 4: return [UIImage imageNamed:@"4StarsSmall"];
- case 5: return [UIImage imageNamed:@"5StarsSmall"];
- }
- return nil;
- }
把tableView:cellForRowAtIndexPath: 改成:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
- Player *player = (self.players)[indexPath.row];
- UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
- nameLabel.text = player.name;
- UILabel *gameLabel = (UILabel *)[cell viewWithTag:101];
- gameLabel.text = player.game;
- UIImageView *ratingImageView = (UIImageView *)[cell viewWithTag:102];
- ratingImageView.image = [self imageForRating:player.rating];
- return cell;
- }
下面运行app ,效果如下:
哎呦~,看起来不怎么对哈,这些cell 都重叠在一起了,你只改变了prototype cell的高度,但是并没有把table view考虑进去。这里有2个解决方案,1 改变tableview 的Row Height 属性或者执行tableView:heightForRowAtIndexPath:
注意:你可能需要hightForRowAtIndexPath,如果你不知道你的cell的具体高度,或者不同的行有不同的高度。
.
回到Main.storyboard,在Table View 的Size inspector 设置Row Height 为55:
再运行app,看起来好多了。
顺路说一嘴,当你通过拖拽cell下沿的方式而不是直接设置值的方式去改变cell的高度,那么table view 的row 高度属性自动会变,当你第一次这么做的使用它就会好用。
Using a Subclass for the Cell
这个table view 已经看起来相当不错了,但我对于通过tag 去获取 label 或者 这个cell 的其他子视图不感冒,把这些labels 与类的outlet连接起来并使用相应性应该是更好的方式,事实证明你可以。通过Objective-C class template为项目添加一个新文件,命名为PlayerCell,并使它继承自,继承自UITableViewCell:
- @interface PlayerCell : UITableViewCell
- @property (nonatomic, weak) IBOutlet UILabel *nameLabel;
- @property (nonatomic, weak) IBOutlet UILabel *gameLabel;
- @property (nonatomic, weak) IBOutlet UIImageView *ratingImageView;
- @end
这个类本身并没有做什么事情,仅仅是增加了几个属性nameLabel,gameLabel以及ratingImageView ,并且是Outlets。
回到Main.storyboard,选中prototype cell,并在Identity inspector中把它的class改成PlayerCell 。现在无论什么时候你通过dequeueReusableCellWithIdentifier向tableview请求一个新的cell 时,它会返回PlayerCell 实例而不是 UITableViewCell 。
注意:这里我们把类名跟reuse identifier 设置成一样了,PlayerCell 这只是我的习惯而已,类名跟reuse identifier 并没有什么关系,所以如果你想起不一样的,随你好了。
下面连接 labels 以及 imageview 的outlet,选择label的onnections inspector 中的New Referencing Outlet 拖拽到 table view cell 上,分别选中nameLabel ,gameLabel:
重点:你需要把controls 与table view cell连接起来,而不是视图控制器。当你的data source 要求那个table view 通过dequeueReusableCellWithIdentifier 要一个新的cell 的时候,table view 并不是给你一个真实的prototype cell,而是复制一份给你(或者重用之前那些cell中一个也说不定)。这就意味着在同一时间不止有一个PlayerCell 的实例。然后不同的label 拷贝视图访问一个outlet,这是自找麻烦(从另一方面,把prototype cell 的 actions连接到 视图控制器上是可行的,当你的cell 中有自定义按钮或者是其他继承于UIControls )
现在你已经连接好这些属性了,你只需要写点点data source代码。首先在PlayersViewController.m中引入PlayerCell 类:
- #import "PlayerCell.h"
然后把cellForRowAtIndexPath 改为:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- PlayerCell *cell = (PlayerCell *)[tableView dequeueReusableCellWithIdentifier:@"PlayerCell"];
- Player *player = (self.players)[indexPath.row];
- cell.nameLabel.text = player.name;
- cell.gameLabel.text = player.game;
- cell.ratingImageView.image = [self imageForRating:player.rating];
- return cell;
- }
更喜欢它了吧,现在你通过dequeueReusableCellWithIdentifier 获取PlayerCell的实例,然后可以设置 它的 labels 以及 imageview。
运行应用,它看起来跟之前没什么不同,但是要注意到,你现在已经用的是自定义的tableviewcell 的子类了。
何去何从?
点击查看该教程的第二部分,它涵盖了segues,静态table view cells以及下载示例工程等。