iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 20
0
Mobile Development

RxSwift / 30天探索之旅系列 第 20

第 20 天 - TableView + Rx 與範例(上)

  • 分享至 

  • xImage
  •  

這系列寫到這階段了,基本觀念跟operator我想都帶到了,於是我想,我們可以來一個簡單範例,將這幾天說提到的,應用在UITableView上,不過在這之前先來講講一下UITableView+Rx的基本用法

TableView + 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)
  1. 給定一個遵守Sequence的items,像是一個Array
  2. 將它bind到tableView的item
  3. 在closure可以收到tableView、row和element參數,裡面就可以自訂cell,最終回傳UITableView的類型即可

第二種方式

把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:基本用法) ,對各項操作都有介紹,非常詳細,所以想說這裡就不細談了!

TableView範例

我自己使用MVVMC架構,不過我想這不影響我們解說,一些觀念會邊寫文章邊帶過,在接下來要進行範例中,程式碼放在GitHub - bing-Guo/RxSwiftDemo,假設我們從API可以取的Product的列表,我們就在把資料呈現在TableView上,很簡單但也滿常見的,所以預計實現了幾項目標

  1. UITableView+Rx的基本使用方式
  2. ViewModel與ViewController的溝通方式
  3. Pull to refresh實現方式
  4. Infinite scroll實現方式

ViewController

畫面部分我習慣用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
}()
  1. 負責資料與邏輯的ViewModel
  2. 存放訂閱者的DisposeBag
  3. 用於呈現載入中的UIRefreshControl
  4. 本次主角的UITableView,並且註冊了ProductListCell這一個自訂的UITableViewCell

初始畫面的部分,因為每個人做法都不一樣,這邊草草帶過

// 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)
}
  1. 要實作infinite scroll,就需要偵測滾到到最後一筆,這部分需要在tableView的tableView(_:willDisplay:forRowAt:)做偵測,很可惜的是RxSwift並沒其封裝,而是提供了tableView.rx.setDelegate(self),讓我們自己編寫Delegate,就跟原生的一樣做法
  2. viewModel.data綁定到UITableView上
  3. 將refreshControl的valueChanged事件綁定到viewModel.triggerAPI,這樣當接收到valueChanged事件就會傳遞給triggerAPI,進而觸發call API
  4. viewModel.isLoading綁定refreshControl.rx.isRefreshing,也就是說當isLoadingtrue時,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的部分。


上一篇
第 19 天 - Schedulers
下一篇
第 21 天 - TableView + Rx 與範例(下)
系列文
RxSwift / 30天探索之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言