iOS 9 Storyboard 教程(一下)

接iOS 9 Storyboard 教程(一上)

上篇链接

原型cell

你可以直接从storyboard编辑器中,使用原型cell你可以很容易的为你的tableViewCell设计一套自定义的布局.

现在的Table View Controller有一个空的原型cell.点击原型cell,你可以在Attributes inspector中设置它的样式(Style)和副标题(Subtitle).

在storyboard中有很多可堆叠的内容,但有时却很难点击你想确切操作的内容.如果你遇到麻烦,下面有几个选项可以帮你.第一个就是在左侧的Document Outline里,你可以选择这个item.第二个是一个方便的热键:按住control+shift并点击你感兴趣区域.会出现一个弹出框让你直接使用光标选择任何元素.

如果你之前使用过table view,兵器手动创建过cell,你可能会认出这是UITableViewCellStyle.带副标题(Subtitle)的样式.和原型cell一样,你也可以选择一个内置的cell样式,就好像你刚才做的一样,或者创建一个自定义设计(你很快机会这样做).

设置Accessory属性为Disclosure Indicator然后把Identifier属性设置PlayerCell.所有的原型cell都应该有一个可重用的标识符(identifier),这样你才能在代码里引用它们.

iOS 9 Storyboard 教程(一下)_第1张图片

运行app,但是好像却没有任何改变…并不是很奇怪:你还必须为tableView添加数据源,这样它才会知道应该显示多少行数据.这正是你接下来要做的.

在工程中添加一个新文件.然后在 iOS/Source选项中,选择Cocoa Touch Class模板.给这个类命名为PlayersViewController并且把它设置为UITableViewController的子类.不选Also create XIB file.

选择Swift语言,然后点击下一步(Next)创建.

iOS 9 Storyboard 教程(一下)_第2张图片

回到storyboard然后选中Table View Controller(确保你选的是实际的视图控制器而不是它里面的某一个视图).在Identity inspector里,设置它的Class是PlayersViewController.对于把刚才创建的类连接到storyboard里的自定义view controller,这是至关重要的一步.不要忘记这一步,否则你刚创建的类将不能使用!

iOS 9 Storyboard 教程(一下)_第3张图片

从现在开始,当你运行app的时候,storyboard 中的table view controller就变成了PlayersViewController类的一个实例.
这个table view应该会显示一列玩家名单,所以现在你需要为这个app创建一个数据模型—一个包含Player对象的数组.使用Swift File模板在iOS/Source里为这个工程添加一个新文件.命名为Player. 替换Player.swift中代码:

import UIKitstruct Player { 
  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 
  }
}

这里没有发生什么特别的事.Player类是一个简单的容器对象,包含有三个属性:玩家的姓名(name),他们正在玩的游戏(game)以及一个额定1至5星的评级(rating).

接下来,你需要把一个Player对象数组赋值给PlayersViewController.使用Swift File模板为开始,创建一个新文件,命名为SampleData.把它添加到SampleData.swift的末尾.

//Set up sample datalet 
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下面添加一个Player 数组:

var players:[Player] = playersData

当定义players变量时,你可以很容易在PlayersViewController里设置样本数据.但由于这些数据在后面也许会从一个plist文件或者SQL文件中取,所以在视图控制器外部加载数据是很明智的.

现在你有一个包含很多Player对象的数组.你可以继续在PlayersViewController里链接数据源.用下面的方法替换table view的数据源:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 
  return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
  return players.count
}

真正的工作发生在cellForRowAtIndexPath.使用下面的代码替换原来的方法:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 
  let cell = tableView.dequeueReusableCellWithIdentifier("PlayerCell", forIndexPath: indexPath) 
  let player = players[indexPath.row] as Player 
  cell.textLabel?.text = player.name 
  cell.detailTextLabel?.text = player.game return cell
}

这个方法dequeueReusableCellWithIdentifier(_:forIndexPath:) 将会检查是否有可用于回收的cell.如果没有,它将自动分配一个原型cell并把它返回给你.你所需要做的就是提供可重用的标识符,你可以在storyboard编辑器里设置原型cell — 在这种情况下的PlayerCell.不要忘记设置标识符,否则这个小的方案将不会起作用! 运行app,看啊,table view上有玩家了!

iOS 9 Storyboard 教程(一下)_第4张图片

只需要几行代码就可以这些原型cell.我觉得那太棒了!


Note:

在这个app,你只用到了一个原型cell,但如果你的table需要显示不同的cell,那么你可以很简单的在storyboard中添加额外的原型cell.你也可以复制已经存在的cell,使它成为一个新的cell,或者增加Table View原型cell的值属性.确保给予了每一个cell属于它自己的标识符.


设计你自己的原型cell

对于很多app来说,使用一个标准的cell样式也是可以的,但是对于这个app来说,如果你想要在cell的右手边添加一张玩家评级(1–5星)的图片.那就需要有一个(图片视图)image view,就目前来看,标准的cell样式是不支持的,所以你必须要自定义设计一个.

切换回Main.storyboard,在 table view,里选择原型cell,并且在Attributes inspector中,设置Style属性为自定义(Custom).现在默认标签已经消失了.

先使cell在高一点.也可以在Size inspector(之后选择自定义)改变Row Height 的值.或者拖动cell的底部,设置高度60.

从Objects Library 拖拽两个Label对象到cell里,把它们粗略的放到标准标签的位置.只要在Attributes Inspector选择你喜欢字体和颜色.设置顶部标签为Name,底部标间为Game.

在Document Outline里同时选中Name和Game标签,然后按住Command+点击,选择Editor\Embed In\Stack View.


Note:

堆栈视图(Stack view)是iOS9新加入的,它可以很容易的布局视图的集合样式.


拖拽一个ImageView到cell并把它放到右侧,在Size Inspector里设置它宽为81高为35.设置它的Mode在中心(Center)(在Attributes inspector下一个),这样无论你把这张图片放置到view的任何地方,它都是不伸展的.

在Document Outline里Command + 点击Stack View和Image View.选择Editor\Embed in\Stack View.Xcode将会创建一个新的水平 stack view 包含这两个控件.

iOS 9 Storyboard 教程(一下)_第5张图片

选中新的水平stack view,在Attributes Inspector里改变Alignment为Centre 并且Distribution改为Equal Spacing. 现在对于这个控制器来说,包含了一些简单地自动布局.在storyboard的右侧底部点击Pin图标:

改变约束为Top: 0, Right: 20, Bottom: 0 and Left: 20.确保这四个红色指针在图片中高亮显示.点击弹出窗口底部的 Add 4 Constraints.

iOS 9 Storyboard 教程(一下)_第6张图片

如果你的stack view有橙色的约束,表明它错位了.为了解决这个问题,选择水平的stack view然后选择Editor\Resolve Auto Layout Issues\Update Frames(在选中的菜单视图部分).这个stack view应该放到正确的位置上,之后橙色的约束错误就会消失了.
为了把image view within 放到 stack view里,在Document Outline里选中image view,然后选择Editor\Resolve Auto Layout Issues\Add Missing Constraints(在选中的菜单视图部分). 最终的为原型cell设计的样子看上去向下面的一样:

iOS 9 Storyboard 教程(一下)_第7张图片

因为这是一个自定义设计的cell,你不能在把UITableViewCell的 textLabel 和 detailTextLabel属性放到标签里了.这些属性所指的标签不再是这个cell里的了;它们只在标准的cell类型里才合法.取而代之的是,你需要使用tag来找到这些标签.
tag被用在这里更加简单.在后面的课程里,你会创建一个自定义的类,继承自UITableViewCell,并且包含对应于你的cell视图的属性. 在Attributes inspector中,设置Name标签的tag值为100,Game标签的tag值为101,以及Image View的tag值为102.
然后打开PlayersViewController.swift,在这个类的底部,添加一个新方法叫做imageForRating.如下代码:

func imageForRating(rating:Int) -> UIImage? { 
  let imageName = "\(rating)Stars" 
  return UIImage(named: imageName)
}

相当简单—根据评级显示不同的星的图标.仍然在PlayersViewController里改变tableView(_:cellForRowAtIndexPath:),如下:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("PlayerCell", forIndexPath: indexPath) //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
}

你所做的会出现崩溃:

1.dequeueReusableCellWithIdentifier将会使用重用标识符PlayerCell,重用已经存在的cell如果不存在就创建一个新的.

2.你查找每一行对应的Player对象,并将其分配给player.

3.可以看到标签和图片的数据都来自player对象.

应该这样做.现在再一次运行app,他看上去好像下面这样:

iOS 9 Storyboard 教程(一下)_第8张图片

恩…,那看起来不太对—cell显示的好像被压扁了一些.你确实改变了原型cell的高度,但 table view 却不这么认为.有两个方法解决这个问题:你可以改变 table view的高度属性,或者实现tableView(tableView:heightForRowAtIndexPath:) 方法.前者是更适用这种情况,因为我们只拥有一种类型的cell,并且我们事先知道cell的高度.


Note:

如果你事先不知道你的cell的高度,或者不同的cell有着不同的高度,你将会使用 tableView(tableView:heightForRowAtIndexPath:).


返回Main.storyboard,在Table View的Size inspector里,设置高度为60.


iOS 9 Storyboard 教程(一下)_第9张图片

如果你现在运行app,它看起来好多了!

iOS 9 Storyboard 教程(一下)_第10张图片

顺便说一句,如果你通过拖拽改变了cell的高度,而没有改变它的值,那么table view的行高属性也会自动改变.所以在第一次可能是正确的.

使用cell的子类

Table view已经非常好了,但是我不是使用tag来访问标签和其他cell子视图的粉丝.如果你能通过连线(outlet)连接这些标签(label),然后使用对应的属性那么它将更干净.事实证明,你可以. 在工程中添加一个新的文件,使用Cocoa Touch Class模板.命名它为PlayerCell,并且把它作为UITableViewCell的子类.不要勾选创建XIB的选项,正如你在storyboard中已经有的cell一样. 添加PlayerCell类的属性,就好像下面的类定义一样:

@IBOutlet weak var gameLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var ratingImageView: UIImageView!

所有的变量都是IBOutlet类型的,它可以连接到当前storyboard中的控制器. 使用IBOutlet就好像下面这样添加属性:

var player: Player! { 
  didSet { 
    gameLabel.text = player.game 
    nameLabel.text = player.name 
    ratingImageView.image = imageForRating(player.rating) 
  }
}

无论当什么时候设置了player的属性,它都会正确地更新IBOutlet里的信息.

把imageForRating(_:)方法从PlayersViewController移动到PlayerCell类里,这样可以在一个类里保持cell的详细信息. 返回Main.storyboard,选中PlayerCell然后在Identity inspector里改变它的类为PlayerCell.

现在无论什么时候你只要改变table view数据源的dequeueReusableCellWithIdentifier(_:forIndexPath:)方法,它都会返回一个PlayerCell实例而不是返回UITableViewCell. 现在你已经给了这个类和重用标识符相同的名字 — 它们都被叫做PlayerCell — 但那只是因为我想要让它们保持一致.类名和重用标识符彼此无关,所以你也可以给它们命名成不同的名字,如果你想那么做的话.

现在将标签和图片视图都连到这些outlet上.在storyboard中导航到Connections Inspector,然后从Document Outline或者工作空间里选择PlayerCell.在Connections inspector中拖拽nameLabel.

给Document Outline中的Name标签对象,或者是拖拽到工作空间中.重复gameLabel和ratingImageView.

iOS 9 Storyboard 教程(一下)_第11张图片

Important:

你应该把控件连接到table view cell上,而不是连接到view controller!你看,只要你的数据源请求table view通过dequeueReusableCellWithIdentifier创建了一个新的cell,那么这个table view将不会调用真正的原型cell,而是一个拷贝(或者如果可能的话,之前有一个cell是可回收的).

这就意味着,在任何给定的时间都有超过一个实例.如果你是将一个标签从cell连接到控制器,那么几个标签的副本将会尝试使用相同的连线.这只是要求麻烦.(在另一方面,将原型cell的活动连接到视图控制器上的动作是非常好的.如果在你的cell上有自定义的按钮或者其他控件,你将会这么做的)


现在你已经连接了这些属性,你可以简化数据源代码.在PlayersViewController,改变tableView(_:cellForRowAtIndexPath:)如下:

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.player = player return cell
}

更像是这样.你可以从dequeueReusableCellWithIdentifier这个方法里得到一个PlayerCell的对象,然后你就可以简单地把正确的玩家信息传递到cell上.在PlayerCell里设置玩家变量将会自动地把值传递到标签和图片视图上,并且cell会使用你在storyboard里的连线.难道使用原型cell使table view变得很整洁不好么?

运行app并且尝试做一下.它依旧会和之前一样,但是在屏幕下面,它使用的是你自己的table view cell的子类!你可以在这里下载所有的源代码.

结语

如果你有任何问题都可以在下面讨论,翻译过程中有个别地方翻译不是十分准确,希望大家批评指正后面会继续更新第二部分,敬请期待!

你可能感兴趣的:(iOS 9 Storyboard 教程(一下))