给UITableView写一个适配器

本文介绍了一种使用Swift语言实现UITableView的Adapter模式的方法,通过抽象出一个Adapter类减少代码重复,并展示了如何实例化和使用该Adapter类。此外,还特别注意UITableView dataSource属性的内存管理问题。

前言

我们在构建一个包含UITableView的UIViewController的时候,总是会写如下的代码: Plain Text tableView.delegate = self tableView.dataSource = self

然后再实现我们需要的一些tableView的委托方法。但是很多时候,这些方法都是重复的,主要的区别就是,dataSource的model不一样。逻辑其实都是相同的,所以,我们可以抽象出一个Adapter,来专门写这些逻辑。

具体的代码

class ListDataSource<T>: NSObject, UITableViewDataSource, UITableViewDelegate {
  
  fileprivate let items: [T]
  
  let cellFactory: (T) -> (UITableViewCell)
  let cellHeightClosure: ((T) -> (CGFloat))?
  
  var scrollViewDidScrollClosure: ((UIScrollView) -> ())?
  
  init(items: [T], cellFactory: @escaping (T) -> (UITableViewCell), cellHeightClosure: ((T) -> (CGFloat))? = nil) {
    self.items = items
    self.cellFactory = cellFactory
    self.cellHeightClosure = cellHeightClosure
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return self.cellFactory(items[indexPath.row])
  }
  
  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    guard let closure = self.cellHeightClosure else {
      return 44.0
    }
    return closure(items[indexPath.row])
  }
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
  }
  
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if let closure = scrollViewDidScrollClosure {
      closure(scrollView)
    }
  }
}
复制代码

然后实例化它,如下:

fileprivate var currentDataSource: (UITableViewDataSource & UITableViewDelegate)? {
    didSet {
      tableView.delegate = currentDataSource
      tableView.dataSource = currentDataSource
      tableView.reloadData()
    }
}

func setDataSource() {
    let cellFactory: (CellDataType) -> (UITableViewCell) = { [unowned self] type in
      switch type {
      case .title(let title):
        let cell = self.tableView.dequeue() as TitleCell
        cell.render(text: title)
        return cell
      case .story(let story):
        let cell = self.tableView.dequeue() as StoryCell
        cell.render(story: story)
        return cell
      }
    }
    let cellHeightClosure: (CellDataType) -> (CGFloat) = { type in
      return type.height
    }
    let dataSource = ListDataSource<CellDataType>(items: self.dataSource, cellFactory: cellFactory, cellHeightClosure: cellHeightClosure)
    dataSource.scrollViewDidScrollClosure = { [unowned self] scrollView in
      self.setupNavigationBar(by: scrollView.contentOffset.y)
    }
    currentDataSource = dataSource
  }
复制代码

当然,这是个比较复杂的例子了,大部分时候,我们其实都只是实现一些很简单的逻辑。对于一些复杂的例子,我们完全可以给它们单独定义一个Adapter,而对于简单的来说,就可以用泛型,来重用这个ListDataSource。

注意的点:

One tricky thing however when you put your dataSource in a dedicated, separate object, is to not forget to retain it.

The dataSource property on UITableView is weak (as all dataSources and delegates should be), so if you just affect tableView.dataSource to a newly created ListDataSource(…) without retaining it, that ListDataSource instance will then be released from memory and your UITableView will go back to being empty.

So when you use that trick, just don’t forget to retain your object serving as DataSource. In the example above, I made the DemoViewController retain it by using a var currentDataSource property, and used that occasion to use a didSet on it to propagate it to the tableView and reload it afterwards.

简单的翻译一下,就是因为UITableView里的dataSource是一个弱变量,所以如果我们给他赋上一个新创建的变量而没有持有它,那么这个变量就会被释放,然后UITableView就会为空。因此我们需要在didSet中给它赋值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值