這系列寫到這階段了,基本觀念跟operator我想都帶到了,於是我想,我們可以來一個簡單範例,將這幾天說提到的,應用在UITableView上,不過在這之前先來講講一下UITableView+Rx的基本用法
UITableView+Rx封裝內容可以藉由搜尋『UITableView+Rx.swift』看到,裡面註解也包含簡單的範例
也是我比較習慣用的方式,cell要自行register
// 1
let items = Observable.just([
"First Item",
"Second Item",
"Third Item"
])
// 2, 3
items
.bind(to: tableView.rx.items) { (tableView, row, element) in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = "\(element) @ row \(row)"
return cell
}
.disposed(by: disposeBag)
把cell register的動作也封裝了起來,就會像下面這樣
let items = Observable.just([
"First Item",
"Second Item",
"Third Item"
])
items
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}
.disposed(by: disposeBag)
其他像是select、didSelect、delete...等都有相對應的寫法,在找尋資料的過重中,發現一個網站 Swift - RxSwift的使用详解30(UITableView的使用1:基本用法) ,對各項操作都有介紹,非常詳細,所以想說這裡就不細談了!
我自己使用MVVMC架構,不過我想這不影響我們解說,一些觀念會邊寫文章邊帶過,在接下來要進行範例中,程式碼放在GitHub - bing-Guo/RxSwiftDemo,假設我們從API可以取的Product的列表,我們就在把資料呈現在TableView上,很簡單但也滿常見的,所以預計實現了幾項目標
畫面部分我習慣用Snapkit,所以,一開始先import所需要的library
import UIKit
import RxSwift
import SnapKit
定義畫面所需要的元件
// MARK: - Private
private let viewModel: ProductListViewModel
private let disposeBag = DisposeBag()
private let refreshControl: UIRefreshControl = {
return UIRefreshControl()
}()
private let tableView: UITableView = {
let tv = UITableView(frame: .zero)
tv.separatorStyle = .none
tv.backgroundColor = .white
tv.estimatedRowHeight = 44
tv.rowHeight = UITableView.automaticDimension
tv.register(ProductListCell.self, forCellReuseIdentifier: String(describing: ProductListCell.self))
return tv
}()
初始畫面的部分,因為每個人做法都不一樣,這邊草草帶過
// MARK: - Constructor
init(viewModel: ProductListViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
setupNavigation()
bindViewModel()
}
func setupTableView() {
self.view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
tableView.addSubview(refreshControl)
}
func setupNavigation() {
navigationItem.title = "範例一"
}
這邊是重點部分,我們將需要與ViewModel做綁定的部分都放在bindViewModel()
func bindViewModel() {
tableView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.data
.bind(to: tableView.rx.items) { (tableView, row, element) in
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ProductListCell.self)) as? ProductListCell else { return UITableViewCell() }
cell.config(item: element)
return cell
}
.disposed(by: disposeBag)
refreshControl.rx.controlEvent(.valueChanged).bind(to: viewModel.triggerAPI).disposed(by: disposeBag)
viewModel.isLoading.bind(to: refreshControl.rx.isRefreshing).disposed(by: disposeBag)
}
tableView(_:willDisplay:forRowAt:)
做偵測,很可惜的是RxSwift並沒其封裝,而是提供了tableView.rx.setDelegate(self)
,讓我們自己編寫Delegate,就跟原生的一樣做法viewModel.data
綁定到UITableView上viewModel.triggerAP
I,這樣當接收到valueChanged事件就會傳遞給triggerAPI,進而觸發call APIviewModel.isLoading
綁定refreshControl.rx.isRefreshing
,也就是說當isLoading
是true
時,refreshControl就會啟動,開始轉圈圈,反之,就會停止。最後,前面提到的,需要偵測滾動到最後一筆。
extension ProductListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastSectionIndex = tableView.numberOfSections - 1
let lastRowIndex = tableView.numberOfRows(inSection: lastSectionIndex) - 1
if indexPath.section == lastSectionIndex && indexPath.row == lastRowIndex {
viewModel.triggerNextPage.onNext(())
}
}
}
今天就先講到ViewController的部分,明天繼續講下半篇ViewModel的部分。