UI基础 - storyboard_swift版 02:Prototype Cell
Prototype Cell
1 -在故事模板中拖进一个 UITableViewController,并设置为初始场景。cell 配置如下
2 - 指定一个 data source:新建 PlayersViewController,它是 UITableViewController 的子类。然后回到 Storyboard,选择表视图控制器,在身份检查器 Identity inspector 中设置它的 Class 为 PlayersViewController,此时 Storyboard 中加载的那个表视图控制器就是 PlayersViewController 的实例
3 - 创建数据模型:Player.swift 文件,它包含玩家名、游戏名、评分
1 import UIKit 2 3 class Player: NSObject { 4 var name: String 5 var game: String 6 var rating: Int 7 8 init(name: String, game: String, rating: Int) { 9 self.name = name 10 self.game = game 11 self.rating = rating 12 super.init() 13 } 14 }
在 PlayersViewController 中赋值到一个数组。请使用 Swift File 模板创建名为 『SampleData』 的新文件,并在 SampleData.swift 中追加以下代码
1 let playersData = [ Player(name:"Bill Evans", game:"Tic-Tac-Toe", rating: 4), 2 Player(name: "Oscar Peterson", game: "Spin the Bottle", rating: 5), 3 Player(name: "Dave Brubeck", game: "Texas Hold 'em Poker", rating: 2) ]
9 - 现在在 PlayersViewController.swift 的 class PlayersTableViewController: UITableViewController 下面添加一个玩家数组属性,用来保存玩家列表
1 var players: [Player] = playersData
说明:这里,你可能会在 PlayersViewController 中定义 players 变量时顺带就把示例数据准备好了,但以后数据可能源自 plist 或 SQL 文件,所以,在视图控制器之外处理数据加载问题是明智之选
10 - 现在你有一个包含多个 Player 对象的数组,可以在 PlayersViewController 中绑定数据源了。还是在 PlayersViewController.swift 中,用以下代码替换表视图数据源方法
1 override func numberOfSections(in tableView: UITableView) -> Int { 2 // #warning Incomplete implementation, return the number of sections 3 return 1 4 } 5 6 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 7 // #warning Incomplete implementation, return the number of rows 8 9 return players.count 10 }
11 - 实际工作在 cellForRowAtIndexPath 中。用以下代码替换方法(原来的注释掉)
1 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 2 3 let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath) 4 as UITableViewCell 5 6 let player = players[indexPath.row] as Player 7 cell.textLabel?.text = player.name 8 cell.detailTextLabel?.text = player.game 9 return cell 10 11 }
说明:dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath) 方法用来检查是否存在可重用的表项。如果没有,就返回一个自动分配的原型表项。你只需要提供之前在 Storyboard 编辑器中给原型表项设定的重用标识符,本例中对应 『PlayerCell』。一定要设置标识符,否则无法正常工作
12 - 运行App,现在表视图中有玩家项了
注: - 该 App 中只使用了一个原型单元格,但如果你的列表需要显示不同种类的单元格,你可以向 Storyboard 中另外添加原型单元格。可以复制现有的单元格再进行修改,也可以增大表视图的 Prototype Cells 属性值。记得每个表项都要设置自己的重用标识符
设计自己的原型单元格
1 - 对很多 App 来说使用内建的标准表项样式已经足够了,但这个 App 需要在表项的右侧添加一个显示评分(1星到5星)的图片。标准表项样式不支持在这里包含图片视图,所以你只能自己创建自定义设计
2 - 切回 Main.storyboard,选择表视图中的原型单元格,在属性检查器中设置 Style 属性为 Custom(自定义),运行,随后默认的 Label 不见了
3 - 首先让表项增高一些,拖动底边上的小方块或在尺寸检查器(Size inspector)中修改 Row Height(行高)值,设置表项高度为55点(points)
4 - 从 Objects Library 拖两个 Label 到表项上,把它们放到和之前的标准样式差不多的地方,你可以在属性检查器中随意设置字体和颜色。设置上面的 Label 文本为『Name』,下面的为『Game』
5 - 把一个 Image View(图片视图)拖到表项中,放在右面紧挨展开方向标的地方,设宽度为81点,高度不是很重要。将其 Mode 设为 Center(在属性检查器的View下面),保证载入视图的图片不会被拉伸
6 - 在尺寸检查器中设 Label 宽度为190点。Label不应盖住 Image View。原型表项的最终设计大概是这个样子
7 - 因为这是一个自定义单元格,所以再也不能用 UITableViewCell 中的 textLabel 和 detailTextLabel 属性来设置文本了。这些属性只在标准表项类型中有效,它们指向的 label 在该表项中已经不存在了。为此,你需要用 tag(标记)找到相应的 label
8 - 你也可以选择创建一个继承 UITableViewCell 的自定义类并包含对应单元格视图中的 label 的属性。而 tag 可以用来简化工作,在简单情况下是很不错的解决方案。不过本教程后面会尝试使用自定义类的方法
9 - 在属性检查器中设置『Name』Label 的 tag 值为100,『Game』Label 为101,Image View 为102
10 - 打开 PlayersViewController.swift,在后面如下添加新方法 imageForRating
1 func imageForRating(rating:Int) -> UIImage? { 2 switch rating { 3 case 1: 4 return UIImage(named: "1StarSmall") 5 case 2: 6 return UIImage(named: "2StarsSmall") 7 case 3: 8 return UIImage(named: "3StarsSmall") 9 case 4: 10 return UIImage(named: "4StarsSmall") 11 case 5: 12 return UIImage(named: "5StarsSmall") 13 default: 14 return nil 15 } 16 }
11 - 很简单,该方法根据评分返回不同的星级图片。依然在 PlayersViewController 中,如下修改 tableView(_:cellForRowAtIndexPath:)方法
1 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 2 3 let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath) as UITableViewCell //1 4 5 let player = players[indexPath.row] as Player //2 6 7 if let nameLabel = cell.viewWithTag(100) as? UILabel { //3 8 nameLabel.text = player.name 9 } 10 if let gameLabel = cell.viewWithTag(101) as? UILabel { 11 gameLabel.text = player.game 12 } 13 if let ratingImageView = cell.viewWithTag(102) as? UIImageView { 14 ratingImageView.image = self.imageForRating(rating: player.rating) 15 } 16 return cell 17 }
说明:① dequeueReusableCell 在回收表项可重用的情况下会抽出重用标识符为PlayerCell的表项,否则创建一个新表项
② 按行号查看 Player 对象并将其赋值给 player
③ 按表项上的 tag 找到 label 和图片,并参照 player 对象填充数据
12 - 应该可以了。现在再次运行 App,大概会像这样
13 - 看起来不大对劲:表项都重叠在一起了。你只修改了原型表项的高度,但是并没有把表视图考虑进去。这里有两个解决方案,一是改变表视图的 Row Height 属性,二是实现 tableView(tableView:heightForRowAtIndexPath:)方法。本例中前者更合适,因为只有一种表项,而且我们已经事先了解表项的高度
注: - 如果无法事先判定表项的高度,或者各行的高度可能不一致,可以使用 tableView(tableView:heightForRowAtIndexPath:)方法
14 - 回到 Main.storyboard,在表视图的尺寸检查器中设 Row Height 为 55 点
15 - 现在运行,一切高大上
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)