[Android開發學iOS系列] TableView展現一個list

2022-11-22 06:00:43

TableView 基礎

本文講講TableView的基本使用.
順便介紹一下delegation.

TableView用來做什麼

TableView用來展示一個很長的list.
和Android中的RecyclerView不同, iOS中的TableView只能是豎直方向的list.

如何寫一個最簡單的TableView

一個最簡單的TableViewController看起來像這樣:

class ViewController: UITableViewController {
    var data: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        // loadData()
        print(data)
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
        cell.textLabel?.text = data[indexPath.row]
        return cell
    }
}

這裡data是想展示的資料型別, 可以hardcode一些資料.

這麼簡單是因為這個ViewController繼承了UITableViewController, 並且cell的部分使用了storyboard.

這裡需要用dequeueReusableCell方法, 是為了cell的複用, 因為list內容很多的時候cell view是可以迴圈使用的. (很像Android裡的RecyclerView).

UITableViewController的簽名是這樣:

open class UITableViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {

它為我們做了以下三件事:

  • 設定view為一個UITableView.
  • 設定delegate=self.
  • 設定dataSource=self.

這種方式的侷限性在於第一點, 它的根view是一個TableView, 如果我們的需求比較複雜, 不僅僅是一個demo, 那麼可能需要組合View.

拆解版TableView

我們也可以直接繼承UIViewController類, 然後自己動手做上面的幾條設定.

Delegate & DataSource

TableView有兩個重要的方面需要關注:

  • UITableViewDelegate: 管理和使用者的互動, 比如選擇, 滑動手勢等. 沒有必須要實現的方法.
  • UITableViewDataSource: 提供和管理資料, 包括了資料對應的cell或者header. 有兩個必須要實現的方法(如上面的程式碼例子所示).

繼承UIViewController

繼承UIViewController而不是UITableViewController之後, 需要自己寫一個tableView並加在view裡.
再分別實現UITableViewDelegateUITableViewDataSource, 這裡寫在extension裡, 拆分完之後set給tableView:

tableView.delegate = self
tableView.dataSource = self

整體改造後程式碼如下:

class ViewController: UIViewController {
    var data: [String] = ["Hello", "World"]

    private let tableView = UITableView()

    override func loadView() {
        view = UIView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ])
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
        tableView.delegate = self
        tableView.dataSource = self
    }
}

extension ViewController: UITableViewDelegate {}

extension ViewController: UITableViewDataSource {
    func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
        data.count
    }

    func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as? MyCell {
            cell.configure(with: data[indexPath.row])
            return cell
        }
        return UITableViewCell()
    }
}

自己的Cell class

這裡Cell也改用程式碼類, 寫一個這樣的類:

class MyCell: UITableViewCell {
    private let label = UILabel()
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: contentView.topAnchor),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
        ])
    }

    @available(*, unavailable)
    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(with data: String) {
        label.text = data
    }
}

注意tableView註冊這個Cell型別:

override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
}

補充知識: Delegation

上面的方法初看可能會非常怪. 這裡還涉及到了一個知識點是iOS中的delegate.
它存在的意義是為了拓展本身類的功能.

Apple自己的很多API就用了delegate protocol, 比如UIApplicationDelegate, UITableViewDelegate.
如果我們想自己定義一個:

protocol MyTypeDelegate: AnyObject {
    func myType(_ myType: MyType,
                      shouldDoSomething argumentString: String) -> Bool

    func myType(_ myType: MyType,
                      didAbortWithError error: Error)

    func myTypeDidFinish(_ myType: MyType)
}

class MyType {
    weak var delegate: MyTypeDelegate?
}

定義delegation的幾個原則:

  • 方法名以被代理的型別開頭.
  • 方法的第一個引數是被代理的物件.

References