本文由CocoaChina翻译小组@TurtleFromMars翻译自:Storyboards Tutorial in Swift: Part 1。ios
更新记录: 该Storyboard教程由Caroline Begbie更新iOS 8和Swift相关内容。原文做者为教程编纂组的成员Matthijs Hollemans。
swift
2014/12/10更新: 更新至Xcode 6.1.1。
数组
Storyboard是最早在iOS 5引入的一项振奋人心的特性,大幅缩减构建App用户界面所需的时间。
浏览器
要介绍Storyboard是什么,我打算从这张图讲起。下面是您将会在本教程中构建的Storyboard:app
或许你如今并不清楚这个App是用来作什么的,但其中有哪些页面,还有页面间的关联都一目了然。这就是使用Storyboard的力量。框架
若是App中包括不少不一样的页面,使用Storyboard能够帮你减小实现页面间跳转的胶合代码。过去的开发者对应每一个视图控制器分别建立界面设计文件(即“nib”或“xib”文件),如今,只要一个Storyboard就能够包揽全部视图控制器的界面设计和他们之间的关联。编辑器
Storyboard有不少优势:ide
使用Storyboard能够更好地了解App中全部的视图以及它们之间的关联的概况。掌控全局更加容易,由于全部的设计都包含在一个文件中,而不是分散在不少单独的nib文件中。工具
Storyboard能够描述不一样视图之间的过渡,这种过渡叫作“segue”(译注:意为“转场”,而“Storyboard”原意为“分镜”,均源自电影术语),你能够直接在Storyboard中经过链接不一样的视图控制器来建立转场。多亏有了转场,打理界面的代码比之前要少了。布局
Storyboard经过新的原型表项(prototype cell)和静态表项(static cell)特性,让处理表视图(table view)的工做更加轻松。几乎彻底能够在Storyboard编辑器里搞定表视图的设计,一样也减小了代码量。
Storyboard使自动布局(Auto Layout)更易用。自动布局功能可让你经过界面元素之间的数学关系定义来肯定元素的位置和尺寸,极大简化了不一样尺寸屏幕的适配工做。自动布局不在本教程范围以内,若想了解更多,请参阅自动布局入门。
若是你很是讨厌Interface Builder,或者推崇用代码搞定全部界面的话,Storyboard可能不适合你。我的主张是代码能少写就少写,特别是UI代码,因此Storyboard简直就是为我准备的一把利器。
若是你想继续使用nib,那就继续用吧,要知道Storyboard里是可使用nib的,二者并不是互斥关系。
本教程中,你会了解Storyboard能够作什么,咱们将构建一个简单的App,功能大体是建立玩家列表和游戏列表,而后给玩家技能评分。过程当中你会学到大多数能够用Storyboard完成的最多见的任务。
打开Xcode,建立新项目。选用 Single View Application 模板:
以下填写模板选项:
Product Name: Ratings
Organization Name: 随意填写
Company Identifier: 你的App使用的标识符,逆域名记法
Language: Swift
Devices: iPhone
Use Core Data: 不选
项目建立完成后,Xcode的主界面应该以下图所示:
这个新项目包含2个类:AppDelegate 和 ViewController, 此外还有本教程的主角: Main.storyboard 文件。
这是一个只支持竖屏显示的App,因此在继续以前,在项目综合设置上面看到的 Deployment Info - Device Orientation下面把 Landscape Left和Landscape Right 选项勾掉。
接下来咱们看一下Storyboard,点击项目浏览器中的 Main.storyboard 就能够在Interface Builder中打开。
一个视图控制器在Storyboard中的官方术语是“场景(scene)”,但这两种叫法是相通的。一个视图控制器在Storyboard中能够叫作场景。
这里能够看到一个包含空视图的视图控制器。在这个视图控制器左边指向它的箭头代表它是这个Storyboard中要显示的第一个视图控制器。
在Storyboard编辑器中设计布局的方法是从右下角的Object Library(对象库)中把控件拖入视图控制器,很是容易。
注:你会注意到默认场景是一个正方形。Xcode 6默认为Storyboard和nib文件开启自动布局(Auto Layout)和尺寸归类(Size Classes)。自动布局和尺寸归类这两项新技术能够构建易于调整大小的用户界面,这对支持不一样尺寸的iPhone和iPad很是有用。
自动布局由iOS 6引入,尺寸归类由iOS 8引入。二者都须要必定的学习曲线,因此本教程中暂不使用,但为了支持不一样的设备尺寸,之后仍是要接触到的。要了解更多,请参阅咱们的书籍 iOS 8教程。
在继续探索以前,先在当前Storyboard的 File inspector(文件检查器) 中禁用Auto Layout和Size Classes,如图:
Xcode询问操做时,选择保留 iPhone 的尺寸归类数据,而后点击 Disable Size Classes :
如今,场景变成了4英寸iPhone尺寸的样子。
从右下方的对象库里把一些控件拖到空的视图控制器上,感觉一下Storyboard编辑器的工做方式:
控件拖进来以后应该会在左边的文档大纲(Document Outline)中显示:
若是没看到文档大纲,请点击Storyboard面板左下角的这个按钮:
Storyboard显示全部视图控制器的内容,当前的Storyboard中仅有一个视图控制器(场景),在本教程后面咱们会添加其余场景。
在场景上面还有一个缩小的文档大纲,称做Dock:
Dock显示场景中最上层的对象,每一个视图都至少有一个 视图控制器(View Controller) 对象,一个 第一响应者(First Responder) 对象,一个 出口(Exit) 项。除此以外也能够有其余的最上层对象。Dock方便链接outlet和action,当你想把某个对象链接到视图控制器时,只需把它拖到Dock的图标上。
注:你可能不经常使用到First Responder。这是指任意对象在任意时间具备第一响应状态的代理对象。举个例子,把一个按钮的Touch Up Inside事件拖到First Responder的 cut:
选择器上。若是在某时有一个文本字段具备输入焦点,此时按下该按钮,就可让该文本字段,也就是如今的第一响应者,把其中的文本剪切到剪贴板。
运行App,它看起来应该和你在编辑器中设计的样子相同(截图可能与你的不一样,仅供演示参考,教程后面不会用到):
你定义的这个视图控制器被设定为初始视图控制器,但App是如何加载的呢?答案就在应用代理(application delegate)当中,打开 AppDelegate.swift ,你会看到以下代码:
1
2
3
4
5
6
7
8
9
10
|
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var
window: UIWindow?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// Override point for customization after application launch.
return
true
}
|
上面的 @UIApplicationMain
标记指定这个AppDelegate类为该模块的入口。使用Storyboard时,应用代理必须继承 UIResponder
,必须含有 UIWindow
属性,几乎全部的方法都是空的,甚至 application(_:didFinishLaunchingWithOptions:)
也只是返回true而已。
秘密藏在 Info.plist 文件里,在Supporting Files Group里找到并点击 Info.plist ,你会看到这一条:
Storyboard用 UIMainStoryboardFile
(即Main storyboard file base name键) 来指明App启动时必须加载的Storyboard的名称。当设置生效,UIApplication
会加载对应名称的Storyboard文件,自动将该Storyboard中的初始视图控制器实例化,并将其归入一个新的 UIWindow
对象中。
在General分页的Project Settings和Deployment Info中也能够看到:
接下来真正开始建立包含多个视图控制器的评分App吧。
你要构建的这个评分App中含有由分页标签控制的两个视图,使用Storyboard建立分页标签很是容易。
如今这个Storyboard须要从头作起,切回 Main.storyboard 而后把刚才作的视图控制器删掉。在文档大纲中点击 View Controller
并按下delete键便可。
把一个 Tab Bar Controller(分页栏控制器) 从对象库拖到面板中。你可能须要让Xcode最大化,由于分页栏控制器附带两个视图控制器,须要腾出更多空间,你能够双击面板进行缩放,或者按住control点击面板,在弹出的菜单中选择缩放比例。
一个新增的分页栏控制器默认附带两个额外的视图控制器,每一个分页标签一个控制器。因为UITabBarController包含一个或多个其余的视图控制器,它被称做 容器视图控制器。此外还有两种常见的容器视图控制器,Navigation Controller(导航控制器)和Split View Controller(分割视图控制器)。
容器关系由分页栏控制器和他所包含的视图控制器之间的箭头表示,以下图这个箭头上的图标表示嵌入关系。
注:若是你想一块儿移动分页栏控制器和附带的视图控制器的话,先缩小画面,而后按住command点击,或直接拖选多个场景,这样能够同时移动多个场景。(选中的场景轮廓为淡蓝色。)
在第一个视图控制器(当前名称为“Item 1”)中拖入一个Label(文本标签)并将其文本设为 "First Tab",同理,在第二个视图控制器中加入文本为"Second Tab"的Label,这样你就能够看到分页标签切换后的变化。
注:编辑器缩小时没法向场景内拖入控件,此时须要先在面板上双击,回到正常缩放比例。
构建,运行,你会在Console中看到相似信息:
Ratings[18955:1293100] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?
幸运的是这条报错信息讲得很清楚:未设置入口,也就是刚才删除最早使用的那个场景以后没设置初始视图控制器。为解决问题,选中这个分页栏控制器,而后在 Attributes Inspector(属性检查器) 中选定 Is Initial View Controller 。
注:在Xcode 6.2中,上述选项已被控件取代。先选中当前分页栏控制器,而后从对象库里把一个Storyboard Entry Point(Storyboard入口)拖上去,能够拖到控制器上面,也能够拖入文档大纲。
如今,一开始的那个箭头已经指向当前的分页栏控制器了:
注:Xcode 6.2 beta在这里可能会崩溃,若是出现问题,请选中该分页栏控制器的某个视图控制器,把入口拖上去,而后再把入口箭头拖到分页栏控制器上。
这意味着启动App时, UIApplication 会把此分页栏控制器做为主画面。运行App试一试,如今App下面有分页标签栏了,能够用分页标签在两个视图控制器之间切换。
提示:你也能够经过拖拽视图控制器之间的箭头来改变初始视图控制器。
其实在前面你也能够选用Xcode自带的标签分页式App模板(即Tabbed Application模板)建立App,但最好仍是了解一下工做原理,之后有必要的时候也能手动建立分页栏控制器。
注:若是在分页栏控制器上链接超过5个场景,App在运行时会自动将其纳入一个More分页标签,干净利落。
如今附属于分页栏控制器的两个场景都是标准UIViewController
实例,接下来你会把其中第一个分页标签对应的场景替换为UITableViewController
。
在文档大纲中点选第一个视图控制器并将其删除,而后从对象库中把一个新的 Table View Controller(表视图控制器)拖到原场景所在的地方。
接下来把表视图控制器放到导航控制器(Navigation Controller)中,选中表视图控制器,在Xcode菜单中选择 EditorEmbed InNavigation Controller ,如今面板中又加入了另外一个控制器:
你也能够从对象库中拖入导航控制器后再嵌入表视图,但这个操做通常来说使用菜单命令会更省时。
与分页栏控制器相似,导航控制器也是容器视图控制器,因此有一个关系箭头指向表视图控制器,你也能够在文档大纲中看到这个关系:
注意,嵌入表视图控制器后,Interface Builder自动给它添加了一个导航栏,由于当前视图是在导航控制器的框架中显示的。它并非实际存在的UINavigationBar
对象,只是模拟显示状况。
打开表视图控制器的属性检查器,上面能够看到 Simulated Metrics(模拟度量)选项:
Storyboard中的默认值为“Inferred(推断)”,意思是该场景在处于导航控制器中时会显示导航栏,处于分页栏控制器中时会显示分页栏等等。你能够修改这些设置,可是请记住,这只是方便你设计界面时参考的模拟显示,并不会在运行时使用,仅仅是视觉设计的辅助工具,用来表示视图最后应该是什么样子。
接下来把这两个新场景链接到分页栏控制器,按住control从分页栏控制器拖到导航控制器,松手时会弹出一个小选单,选择 Relationship Segue – view controllers 选项:
这会在两个场景间新建一个关系箭头,与分页栏控制器包含控制器同样,都是嵌入关系。
分页栏控制器有两个嵌入关系,分别对应两个分页标签。导航控制器上有一个表视图控制器的嵌入关系。
建立这个链接后,分页栏控制器中会添加一个新分页标签,默认名称为“Item”。在这个App中,你但愿第一个分页标签对应这个新场景,直接拖动分页标签,更改顺序:
运行App试试看,如今第一个分页标签中包含一个嵌入在导航控制器中的表视图。
在添加实际功能以前,你还须要再修整一下Storyboard,将第一个分页标签命名为"Players",第二个命名为"Gestures"。不是在分页栏控制器上修改,而是在这些分页标签对应的视图控制器上修改。
将一个视图控制器链接到分页栏控制器后,在场景下面和文档大纲中会看到它被赋予的 分页栏项(Tab Bar Item) 对象,能够用来设置分页标签的标题和在分页栏控制器中看到的图标。
选中导航控制器中的分页栏项,在属性检查器中将标题设为Players:
以一样的方法把第二个分页标签对应场景栏目更名为Gestures。
一个设计精良的App应该为分页标签附上图标。教程资源中有个Image文件夹,把这个文件夹拖入项目,选择“Copy items if needed”并点击Finish:
在Players分页栏项的属性检查器中选择图片 Players.png 。
你可能已经想到了,给Gestures选择 Gestures.png 。
嵌入导航控制器的一个视图控制器包含用于设置导航栏的 Navigation Item(导航项) 。在文档大纲中选择表视图控制器的导航项,在属性检查器中把Title改为Players。
或者你也能够双击导航栏直接修改title,注意你须要双击的是表视图控制器中的模拟导航栏,而不是导航控制器中的那个导航栏对象。
运行App,欣赏一下这漂亮的分页标签栏吧!一行代码也不用写哦!
原型表项容许你直接在Storyboard编辑器中为表视图设计自定义布局。
表视图控制器默认会带一个空的原型表项。点击它,在属性检查器中设置Style为 Subtitle(副标题)。这会当即改变表项的外观,使其包含两个Label。
Storyboard上能够堆叠不少内容,有时可能很难点击到你想选中的东西。若是遇到困难,有几种选择:第一是在面板左侧的文档大纲中选择,第二是快捷键(按住control+option+shift,点击想选择的区域后会弹出指针所指区域的全部元素),第三种选择是Xcode 6的新功能,反复点击能够在各层之间循环。
若是你以前用过表视图,还手动建立过本身的表项,你可能会将其认做UITableViewCellStyle.Subtitle
样式。有了原型表项,你能够像刚才那样选择系统内建的样式,也能够自定义设计(咱们稍后就要建立了)。
设置Accessory(附件,即表项右侧的附属元素)属性为 Disclosure Indicator(展开方向标,即右键头),并在 Identifier(标识符) 字段中输入 PlayerCell。全部的原型表项仍然是标准UITableViewCell
对象,因此它们须要一个以供重用的标识符。
运行应用……什么都没变。这没什么值得奇怪的,接下来你还须要为这个表指定一个data source(数据源),这样它才会知道要显示什么。
在项目中添加一个新文件,选择iOS/Source下的Cocoa Touch Class模板,命名为 PlayersViewController ,并确保它是UITableViewController的子类。不要选中Also create XIB file选项,由于你已经在Storyboard中设计好了,今天不用nib!选择Swift语言,点击Next,而后点击Create。
回到Storyboard,选择表视图控制器(确保你选择的是视图控制器而不是其中包含的某个视图)。在身份检查器(Identity inspector)中设置它的 Class 为 PlayersViewController。这对于在Storyboard场景中使用自定义视图控制器的子类很重要,由于若是你不这么作,你的类就都不会被使用!
此后运行App时Storyboard中加载的那个表视图控制器就是PlayersViewController
类的实例。
这个表视图要显示玩家列表,因此你须要为App建立主要的数据模型:一个包含Player对象的数组。由iOS/Source下的Swift File模板添加新文件,命名为Player。
在Player.swift中追加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import UIKit
class Player: NSObject {
var
name: String
var
game: String
var
rating: Int
init(name: String, game: String, rating: Int) {
self.name = name
self.game = game
self.rating = rating
super
.init()
}
}
|
没什么特别的东西,Player
只是容器对象,其中包含三个属性:玩家名称,进行的游戏,还有1到5星之间的评分。
接下来要建立一组Player
测试对象,并在PlayersViewController
中赋值到一个数组。请使用Swift File模板建立名为SampleData的新文件,并在SampleData.swift中追加如下代码:
1
2
3
4
5
|
//Set up sample data
let playersData = [ Player(name:
"Bill Evans"
, game:
"Tic-Tac-Toe"
, rating: 4),
Player(name:
"Oscar Peterson"
, game:
"Spin the Bottle"
, rating: 5),
Player(name:
"Dave Brubeck"
, game:
"Texas Hold 'em Poker"
, rating: 2) ]
|
这里定义了一个叫作playersData的常量,并把写定的Player对象数组赋值给它。
如今在PlayersViewController.swift的class PlayersTableViewController: UITableViewController
下面添加一个玩家数组属性,用来保存玩家列表:
1
|
var
players: [Player] = playersData
|
这里,你可能会在PlayersViewController中定义players变量时顺带就把示例数据准备好了,但之后数据可能源自plist或SQL文件,因此,在视图控制器以外处理数据加载问题是明智之选。
如今你有一个包含多个Player
对象的数组,能够在PlayersViewController
中绑定数据源了。仍是在PlayersViewController.swift中,用如下代码替换表视图数据源方法:
1
2
3
4
5
6
7
|
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return
1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return
players.count
}
|
实际工做在cellForRowAtIndexPath
中。用如下代码替换方法(原来的注释掉):
1
2
3
4
5
6
7
8
9
10
|
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(
"PlayerCell"
, forIndexPath: indexPath)
as UITableViewCell
let player = players[indexPath.row] as Player
cell.textLabel?.text = player.name
cell.detailTextLabel?.text = player.game
return
cell
}
|
dequeueReusableCellWithIdentifier(_:forIndexPath:)
方法用来检查是否存在可重用的表项。若是没有,就返回一个自动分配的原型表项。你只须要提供以前在Storyboard编辑器中给原型表项设定的重用标识符,本例中对应PlayerCell。必定要设置标识符,不然没法正常工做!
运行App,如今表视图中有玩家项了!
只要写几行代码就可使用原型表项,赞!
注:该App中只使用了一个原型表项,但若是你的列表须要显示不一样种类的表项,你能够向Storyboard中另外添加原型表项。能够复制现有的表项再进行修改,也能够增大表视图的Prototype Cells属性值。记得每一个表项都要设置本身的重用标识符。
对不少App来讲使用内建的标准表项样式已经足够了,但这个App须要在表项的右侧添加一个显示评分(1星到5星)的图片。标准表项样式不支持在这里包含图片视图,因此你只能本身建立自定义设计。
切回Main.storyboard,选择表视图中的原型表项,在属性检查器中设置Style属性为Custom(自定义),随后默认的Label不见了。
首先让表项增高一些,拖动底边上的小方块或在尺寸检查器(Size inspector)中修改Row Height(行高)值,设置表项高度为55点(points)。
从Objects Library拖两个Label到表项上,把它们放到和以前的标准样式差很少的地方,你能够在属性检查器中随意设置字体和颜色。设置上面的Label文本为“Name”,下面的为“Game”。
把一个Image View(图片视图)拖到表项中,放在右面紧挨展开方向标的地方,设宽度为81点,高度不是很重要。将其Mode设为Center(在属性检查器的View下面),保证载入视图的图片不会被拉伸。
在尺寸检查器中设Label宽度为190点。Label不该盖住Image View。原型表项的最终设计大概是这个样子:
由于这是一个自定义表项,因此不再能用 UITableViewCell
中的textLabel
和detailTextLabel
属性来设置文本了。这些属性只在标准表项类型中有效,它们指向的label在该表项中已经不存在了。为此,你须要用tag(标记)找到相应的label。
你也能够选择建立一个继承UITableViewCell的自定义类并包含对应表项视图中的label的属性。而tag能够用来简化工做,在简单状况下是很不错的解决方案。不过本教程后面会尝试使用自定义类的方法。
在属性检查器中设置“Name”Label的tag值为100,“Game”Label为101,Image View为102.
打开PlayersViewController.swift,在后面以下添加新方法imageForRating
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func imageForRating(rating:Int) -> UIImage? {
switch
rating {
case
1:
return
UIImage(named:
"1StarSmall"
)
case
2:
return
UIImage(named:
"2StarsSmall"
)
case
3:
return
UIImage(named:
"3StarsSmall"
)
case
4:
return
UIImage(named:
"4StarsSmall"
)
case
5:
return
UIImage(named:
"5StarsSmall"
)
default
:
return
nil
}
}
|
很简单,该方法根据评分返回不一样的星级图片。依然在PlayersViewController中,以下修改tableView(_:cellForRowAtIndexPath:)
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(
"PlayerCell"
, forIndexPath: indexPath) as UITableViewCell
//1
let player = players[indexPath.row] as Player
//2
if
let nameLabel = cell.viewWithTag(100) as? UILabel {
//3
nameLabel.text = player.name
}
if
let gameLabel = cell.viewWithTag(101) as? UILabel {
gameLabel.text = player.game
}
if
let ratingImageView = cell.viewWithTag(102) as? UIImageView {
ratingImageView.image = self.imageForRating(player.rating)
}
return
cell
}
|
讲解一下刚才作的工做:
dequeueReusableCellWithIdentifier
在回收表项可重用的状况下会抽出重用标识符为PlayerCell
的表项,不然建立一个新表项。
按行号查看Player
对象并将其赋值给player
。
按表项上的tag找到label和图片,并参照player
对象填充数据。
应该能够了。如今再次运行App,大概会像这样:
嗯,看起来不大对劲,表项都重叠在一块儿了。你只修改了原型表项的高度,可是并无把表视图考虑进去。这里有两个解决方案,一是改变表视图的Row Height属性,二是实现tableView(tableView:heightForRowAtIndexPath:)
方法。本例中前者更合适,由于只有一种表项,并且咱们已经事先了解表项的高度。
注:若是没法事先断定表项的高度,或者各行的高度可能不一致,可使用tableView(tableView:heightForRowAtIndexPath:)
方法。
回到Main.storyboard,在表视图的尺寸检查器中设Row Height为55点:
如今运行,看起来好多了!
哦,还有一点,若是以前修改表项高度时没有手动输入数据,而是拖动表项边上的小方块的话,表视图的行高属性也会自动随之改变。因此在构建过程当中你可能并没碰到上述问题。
这个表视图用起来已经至关不错了,但我不大喜欢用tag来获取原型表项的子视图。若是可能的话,把这些label于outlet链接并使用相应属性要优雅得多。事实是可行的。
在项目中以Cocoa Touch Class模板添加一个新文件,命名为PlayerCell并令其继承UITableViewCell。不要选中建立XIB的选项,由于Storyboard里已经有表项了。
在PlayerCell类的类定义下面添加如下属性
1
2
3
|
@IBOutlet weak
var
gameLabel: UILabel!
@IBOutlet weak
var
nameLabel: UILabel!
@IBOutlet weak
var
ratingImageView: UIImageView!
|
这些变量都是IBOutlet
,它们能够在Storyboard中与场景创建链接。
回到Main.storyboard,选中原型表项PlayerCell,并在身份检查器中把它的class改为PlayerCell。如今每当经过dequeueReusableCellWithIdentifier(_:forIndexPath:)
向表视图请求一个新表项时,它会返回PlayerCell实例而不是UITableViewCell。
注意:这里咱们把类名跟重用标识符设置成同样了,都是PlayerCell。这只是由于我的喜欢保持一致,类名跟重用标识符绝不相干,若是你愿意,也能够起不一样的名字。
下面令label以及image view与outlet链接。在Storyboard中切到链接检查器(Connections inspector),而后在面板或文档大纲中选择Player Cell,把链接检查器中的nameLabel outlet拖到Name label对象上。对gameLabel和ratingImageView执行一样操做。
重点:控件要链接的是表项,而不是视图控制器!当你的数据源向表视图经过dequeueReusableCellWithIdentifier
索求一个新表项的时候,表视图并非把原型表项交给你,而是复制一份给你(或是以前被归入回收空间的一个已有表项)。
这就意味着在同一时间不止有一个PlayerCell的实例,若是把表项中的label链接到了视图控制器的outlet上,不一样的label拷贝会试图使用同一个outlet,这是自找麻烦。(另外一方面,把原型表项链接到视图控制器的action上是可行的,当你的表项中含有自定义按钮或者是其余UIControl
时可能会用到。)
除使用链接检查器以外,你也能够按住control从PlayerCell拖到控件上,而后在弹出的选单中选择outlet名称。
如今已经绑定属性,能够稍微简化数据源的代码。在PlayersViewController中以下修改tableView(_:cellForRowAtIndexPath:)
方法:
1
2
3
4
5
6
7
8
9
10
11
|
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(
"PlayerCell"
, forIndexPath: indexPath)
as PlayerCell
let player = players[indexPath.row] as Player
cell.nameLabel.text = player.name
cell.gameLabel.text = player.game
cell.ratingImageView.image = imageForRating(player.rating)
return
cell
}
|
这就更像样了。把从dequeueReusableCellWithIdentifier
接收的对象转为一个PlayerCell
,而后就可使用链接到label和图片视图的属性。这样使用原型表项,表视图不像之前那么乱了。
运行App试试看。看起来应该和以前同样,但在幕后,如今使用的已是你本身的表项子类了!