`
dawuafang
  • 浏览: 1108080 次
文章分类
社区版块
存档分类
最新评论

iOS 5 故事板入门(2)

 
阅读更多

原文: http://www.raywenderlich.com/5138/beginning-storyboards-in-ios-5-part-1


模板 cells

注意到当你加入 tableViewController 后,Xcode 会发出警告了吗?

“Unsupported Configuration: Prototypetable cells must have reuse identifiers”,当加入一个TableViewController 到故事板后,Xcode 默认会使用一种 prototype cells 的单元格(模板cells)。但我们并没有配置它,因此会有这个警告。

模板 cells 是一种很酷的故事板特性。它远胜于原来的 nib 文件。在以前,如果你要定制表视图单元格,你要么在代码中向cell 对象添加自己的 subviews ,要么新建一个 nib 然后从 nib 中加载你自己的 cell。但模板 cells 的出现简化了这一切,现在你可以直接在故事板编辑器中设计你自己的表视图单元格。

TableViewController 上自带有一个空白的模板 cell。点击这个cell,你可以在属性面板中设置它的样式为 Subtitle。这会使 cell 变成包含有两个 label 的 cell。如果你曾经自己手动创建过TableViewCell,你应该知道这就是UITableViewCellStyleSubtitle 样式。通过模板 cells,你可以创建内置样式的 cell,也可以创建完全定制的cell(我们马上就会提到)。

将 Accessory 属性改为Disclosure Indicator 然后将 Reuse Identifier (复用ID) 设置为“PlayerCell”。这样 Xcode 会立即消除警告。所有的模板 cells 仍然是普通的 UITableViewCell 对象,仍然会带有一个复用 ID,Xcode仅仅是提示我们别忘了设置它(至少会让我们注意到这个警告)。

运行程序,什么都没有改变。不要奇怪,我们还没有提供数据源,因此表视图中不会显示任何行。

加一个新的 File 到项目中。选择 UIViewControllersubclass 模板。将类命名为 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 (玩家)对象。现在创建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星)。

我们将在 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 中创建我们的数据源了。

打开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 {

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 代码。但现在不同了。将代码修改为:

- (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只需一行代码,这是件了不起的事情!

设计完全自定义的模板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 属性也会自动随之改变。

再次运行程序,这次看起来就好多了。

子类化模板 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"

@implementation PlayerCell

@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 时对这些细节性的东西一清二楚。

接下来的内容

在本教程的 第二部分,我们囊括了如下内容: segues、静态 tableViewCell、添加玩家窗口、游戏选择窗口以及本教程的示例代码!


分享到:
评论

相关推荐

    IOS Storyboard 入门源码

    Storyboard是一项令人兴奋的功能,在iOS5中首次推出,在开发app的界面时可以极大地节省时间。 如下图所示,这就是一个完整的应用的storyboard,接下来我们要学习如何通过这种方式创建应用。 现 ...

    IOs 5编程入门经典

    ◆ 介绍了iOS 5编程的基础知识 ◆ 介绍如何响应及处理设备旋转以及不同类型的屏幕方向 ◆ 展示如何使用Tabbed、Master-Detail、Single View和Utility等 各种应用程序模板 ◆ 探讨在Master-Detail Application中使用...

    iOS开发入门心得与实战经验分享.zip

    内容概要:这篇文章是一篇关于iOS开发的入门心得和实战经验分享。作者以通俗易懂的语言,介绍了iOS开发的基本概念,如UIKit、AutoLayout、Delegate等,并分享了自己在实际项目中使用iOS开发的经验和心得。 适用人群...

    swift 10 个 新手入门项目 demo ios苹果开发之一(11-20)

    swift 10 个 新手入门项目 demo ios苹果开发之一(11-20) swift 10 个 新手入门项目 demo ios苹果开发之一(11-20) https://github.com/soapyigu/Swift30Projects

    iOS开发入门1.docx

    iOS开发入门1 Application Kit框架包含广泛的类和方法,它们能够开发交互图形应用程序,使得开发文本、菜单、工具栏、表、文档、剪贴板和窗口等应用变得十分简便,用于提供与窗口、按钮、列表等相关的类。

    Play2:使用 djinni 和 gyp 的 iOS 和 Android 应用入门模板

    Play2 是用于创建共享通用非 UI 核心的 iOS 和 Android 应用程序的模板。 将您的新 iOS 和 Android 应用程序基于 Play2 基础架构,以最大限度地重用代码并节省开发时间。 如果您同时在两个平台上开发应用程序,您...

    Unity 5.x 从入门到精通.高清pdf下载 完整版

    Unity 5 引擎支持包括 Mac OS X、安卓、iOS、Windows 等在内的23个平台发布。目前,利用Unity游戏引擎开发的游戏终端有手机、平板和台式机等。有多所高校购买了Unity软件,并在高校的课程中开始讲授。, 本书适用于...

    unity5.X入门到精通PDF扫描高清版

    Unity 5 引擎支持包括 Mac OS X、安卓、iOS、Windows 等在内的23个平台发布。目前,利用Unity游戏引擎开发的游戏终端有手机、平板和台式机等。有多所高校购买了Unity软件,并在高校的课程中开始讲授。, 本书适用于对...

    Unity 5.X从入门到精通 PDF

    Unity5.X从入门到精通基于Unity 5.0软件及以上版本,对Unity 5 中的大量图形改进和扩展的编辑器功能集进行讲解。另外,对Unity Cloud Build的使用进行详细介绍,使开发者通过云进行游戏和应用开发。 为了使Unity 3D...

    HTML5 游戏开发入门教程.docx

    包括 iOS 和 Android 等手机和平板电脑。 2.低成本 HTML5 游戏开发无需购买专门的开发工具,开发者只需使用普通的文本编 辑器即可。这意味着开发成本低,适合初学者入门。 3.易于维护 HTML5 游戏采用的是 Web 标准...

    Unity 5.x 从入门到精通.pdf.7z-扫描版

    《Unity 5.X从入门到精通》基于Unity 5.0软件及以上版本,对Unity 5 中的大量图形改进和扩展的编辑器功能集进行讲解。另外,对Unity Cloud Build的使用进行详细介绍,使开发者通过云进行游戏和应用开发。  为了使...

    Unity 5.X从入门到精通.pdf 有目录【高清1.2G】

    《Unity 5.X从入门到精通》基于Unity 5.0软件及以上版本,对Unity 5 中的大量图形改进和扩展的编辑器功能集进行讲解。另外,对Unity Cloud Build的使用进行详细介绍,使开发者通过云进行游戏和应用开发。  为了使...

    iOSCustomKeyboard:iOS 8 上的自定义键盘扩展模板,使用故事板和自动布局用 Obj-C 编写

    这个通用键盘模板使用 Objective-C 编写并使用故事板和自动布局,是一个完整的入门套件,适合任何想要创建自定义键盘的人。 所有基本键都已创建,并且键盘可以以与系统键盘相同的方式在数字和符号模式之间切换。 ...

    FastApp:由Objective-C和Swift编写的iOS入门工具包

    由Swift和Objective-C编写的面向iOS开发人员的入门工具包 使用FastApp ,可以使创建标准iOS应用程序更加简单。 FastApp建立在主流框架上,并使用Masonry进行自动布局。 内置框架 石工 AF网络 MBProgressHUD ...

    ios iphone app 源代码,HelloWorld样例

    一个最简单的ios app源代码,希望对刚入门的兄弟有帮助

    flutter_boilerplate:我的Flutter Android和iOS应用程序入门模板

    颤振休息起动器我的Flutter入门应用程序用于具有REST后端的项目。 该软件包的主要目的是使您在下一个Flutter项目中尽快启动并运行,而无需进行初始项目设置的所有麻烦。 注意:可能在所使用的软件包和样式中有些...

    Swift5语言入门实例教程PPT模板完整版

    Swift,苹果于2014年WWDC苹果开发者大会发布的新开发语言,可与Objective-C共同运行于macOS和iOS平台,用于搭建基于苹果平台的应用程序。 Swift是一款易学易用的编程语言,而且它还是第一套具有与脚本语言同样的表现...

    Unity 5.x 从入门到精通.pdf

    Unity 5 引擎支持包括 Mac OS X、安卓、iOS、Windows 等在内的23个平台发布。目前,利用Unity游戏引擎开发的游戏终端有手机、平板和台式机等。有多所高校购买了Unity软件,并在高校的课程中开始讲授。, 本书适用于对...

Global site tag (gtag.js) - Google Analytics