[Swift] UITableView:静态 TableView 的简化之道

2016-12-29 22:21:17 +08:00
 banxi1988

前言

首先以 "微信" 应用我的界面为例, 怎么让快速的实现如下的界面呢? 可以先猜一下,我会给出什么样的简化方案.

我的解决方案

好了, 废话不多话, 步骤也省了. 直接上结果: StaticTableViewAdapter

直接以一个 Adapter 作为 UITableViewCell 的容器.

public protocol StaticHeightAware{
  var staticHeight: CGFloat{ get }
}

public class StaticTableViewAdapter: NSObject, UITableViewDataSource, UITableViewDelegate{
  public private(set) var cells:[UITableViewCell] = []
  private weak var tableView: UITableView?
  public var didTapCell: ((UITableViewCell) -> Void)?
  
  init(cells: [UITableViewCell]){
    self.cells = cells
  }
  
  public func bind(to tableView: UITableView){
    self.tableView = tableView
    tableView.dataSource = self
    tableView.delegate = self
  }
  
  public func staticCell(atIndexPath indexPath: IndexPath) -> UITableViewCell{
    return cells[indexPath.row]
  }
  
  public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return cells.count
  }
  
  public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return cells[indexPath.row]
  }

  // MARK: UITableViewDelegate
  public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let cell = staticCell(atIndexPath: indexPath)
    if let heightAware = cell as? StaticHeightAware{
      return heightAware.staticHeight
    }
    return tableView.rowHeight
  }
  
  public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    didTapCell?(staticCell(atIndexPath: indexPath))
    tableView.deselectRow(at: indexPath, animated: true)
  }
  
}

空白的间隔可以以一个 EmptyCell 来实现.作为空白的 Hack. 可以指定高度.

public class EmptyCell: UITableViewCell, StaticHeightAware{
  public var staticHeight: CGFloat = 15
  public init(height: CGFloat = 15){
    super.init(style: .default, reuseIdentifier: "emptyCell")
    staticHeight = height
    contentView.backgroundColor = UIColor(white: 0.932, alpha: 1.0)
  }
  
  required public init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

实现此列表

func makeEntryCell(title:String) -> UITableViewCell{
  let cell = UITableViewCell(style: .default, reuseIdentifier: "entryCell")
  cell.textLabel?.text = title
  return cell
}


let albumCell = makeEntryCell(title: "相册")
let favoriteCell = makeEntryCell(title: "收藏")
let walletCell = makeEntryCell(title: "钱包")
let cardCell = makeEntryCell(title: "卡包")
let emotionCell = makeEntryCell(title: "表情")
let settingsCell = makeEntryCell(title: "设置")

let adapter = StaticTableViewAdapter(cells: [
  albumCell, favoriteCell, EmptyCell(), walletCell, cardCell, EmptyCell(), emotionCell, EmptyCell(),settingsCell
  ])


let tableView = UITableView(frame: CGRect(x:0,y:0,width:320,height:480), style: .plain)
tableView.backgroundColor = UIColor(white: 0.932, alpha: 1.0)
tableView.tableFooterView = UIView()
adapter.bind(to: tableView)

adapter.didTapCell = { cell in
  switch cell {
  case albumCell:
    NSLog("Did Tap Cell albumCell")
  default:
    break
  }
}

最后,我这只是抛砖引玉, 各位可以根据自己的需要再进一步扩展. 有什么不足,敬请指正. 实例截图:

4445 次点击
所在节点    iDev
9 条回复
aaronlam
2016-12-29 22:32:09 +08:00
为什么截图是个安卓?这很不苹果!
banxi1988
2016-12-29 23:08:30 +08:00
@aaronlam Sorry, 我没苹果手机.
aaronlam
2016-12-30 00:19:17 +08:00
@banxi1988 O(∩_∩)O 开个玩笑。
free9fw
2016-12-30 00:50:38 +08:00
还是太复杂了,用同一个 cell 大概只要 60 行代码搞定
CommandZi
2016-12-30 10:16:44 +08:00
很明显微信用的是 UITableViewStyleGrouped 样式的 UITableView 。
你用空白 Cell 代表作为中间的间隔已经很不优雅了,而且你的分割线都没处理好。
我觉得你把简单的问题复杂化了。
banxi1988
2016-12-30 11:19:30 +08:00
@CommandZi
首先感谢你的回复.

这里再谈谈我的想法

一: 为什么说用 GroupTableView 不好.
1. 使用 Group TableView 其实对于 heightForHeaderInSection 感觉很难控制高度, 我有时搞得对, 现在又忘记了. 系统默认的高度要高一些. 但是国内应用好像普通要矮一些.

2. 如果使用 Group TableView 相当于是使用多维数组了. 二维的比一维的复杂. 这个道理比较简单.

3. 你说到分割线没有处理好, 这个我承认. 但是我这个 Demo 主要目的是 简化这个静态的处理.
**但是** 但是, 如果使用 Group TableView 这个分隔线的处理也麻烦. 对于我来说,他们的解决方案是一样的:
你需要 1) 使用 `tableView.separatorStyle = .none` 分隔线隐藏. 但是同一个 Section 中的分隔线又需要显示.
2) 对于中间的 Cell 在下面 添加一个 ShapeLayer 来当作分隔线.



二: 如果确实需要 GroupTableView

也可以封装一个 `GroupTableViewAdapter` 它不是 Cell 数组, 而是 `GroupSection` 数组.

```swift
public struct GroupSection{
let cells:[UITableViewCell]
}
```
然后:

```swift
let adapter = GroupTableViewAdapter(sections: [
GroupSection(cells: [albumCell, favoriteCell]),
GroupSection(cells: [walletCell, cardCell]),
GroupSection(cells: [emotionCell]),
GroupSection(cells: [settingsCell]),
])
```
当然, 我觉得这比不使用 GroupSection 复杂了.

三:
综上: 对于这种场景, 不使用 GroupTableView 挺好的.

对了,使用 EmptyCell 作为中间间隔其实挺好的啊. 没什么不优雅. 方便灵活.
如果需要你也可以当. TableViewSectionHeaderFooterView 来用
yfmir
2016-12-30 11:25:55 +08:00
Storyboard+static cell 路过
banxi1988
2016-12-30 12:09:45 +08:00
@yfmir 我觉得代码写起来更方便快捷,修改更方便.
再者,Storyboard 打开都得半天. 我已经不喜欢 Storyboard 了.
C90
2017-04-11 17:01:36 +08:00
@banxi1988 只能说明你需要换台电脑。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/331111

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX