[RxSwift] TableView 공부하기 (1)

CoCoa TableView는 저번에 다 했으니, RxSwift에서 TableView를 사용하는 방법에 대해 알아보도록 하자

RxSwift를 이용하여 TableView 만들기

우선 프로젝트를 하나 만들고 기본적인 초기 세팅을 하자

  1. Swift Package Manager로 RxSwift/RxCocoa/RxDatasoruce 추가

  1. 프로젝트 구조를 View/Model/ViewModel로 구분

  1. ViewController를 Navigation Controller에 embed하고 스토리보드로 ViewController에 tableView와 cell(identifier = "cell")을 추가

모델 작성하기

애플의 여러 제품들을 테이블뷰에 보여지게 하려고 한다
제품이름과 제품가격을 보여줄거고, 기본 cell type을 쓰려고한다

모델은 다음과 같이 정의했다

import Foundation
struct Product {
    let name: String
    let price: Int
        
    var printPrice: String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter.string(for: price) ?? "0" + "원"
    }
}

뷰모델 작성하기

뷰모델에서는 데이터를 내보내줄 observable이 있으면 된다
BehaviorRelay와 just 연산자를 활용해 데이터를 내보내는 옵져버블 data를 만들었다

import RxRelay

struct MainViewModel {
    let data = BehaviorRelay<[Product]>.just([
        Product(name: "iPhone 13 Mini", price: 950000),
        Product(name: "iPhone 13", price: 1090000),
        Product(name: "iPhone 13 Pro", price: 1350000),
        Product(name: "iPhone 13 Pro Max", price: 1490000),
        Product(name: "MacBook Air", price: 1290000),
        Product(name: "MacBook Pro 14", price: 2690000),
        Product(name: "MacBook Pro 16", price: 3360000),
        Product(name: "iPad 9th", price: 449000),
        Product(name: "iPad Air", price: 779000),
        Product(name: "iPad Mini", price: 649000),
        Product(name: "iPad Pro 12.9", price: 1379000),
    ])
}

기존 Cocoa 패턴에서는 delegate와 datasource를 이용해서 tableView를 컨트롤 했는데, RxSwift에서 이런 작업은 필요하지 않다

bind함수 작성하기

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {
    let bag = DisposeBag()
    let viewModel = MainViewModel()
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bind(viewModel)
        attribute()
    }
}

private extension ViewController {
    func bind(_ viewModel: MainViewModel) {
        viewModel.data.bind(to: tableView.rx.items(cellIdentifier: "cell")){ index, model, cell in
            cell.textLabel?.text = model.name
            cell.detailTextLabel?.text = model.printPrice
        }
        .disposed(by: bag)
    }
    func attribute() {
        title = "Apple 신제품"
    }
}

아주 잘나오는것을 확인할 수 있다

보다시피 RxSwift로도 충분히 tableView를 구현할 수 있는데, RxDatasource가 필요한 이유는?
만약 tableView가 여러 section을 가지거나, Animation이 필요하다면 필수적으로 구현해줘야함
(그니깐 매우매우매우매우 간단한 형태말고는 사실상 무조건 rxdatasource로 구현해야함..)

RxDataSource를 이용해서 TableView 만들기

위에서 그냥 표현했던 제품군들을 제품군(아이패드, 맥북, 아이폰)에 따라서 섹션으로 분류해보자
근데 한번도 해본적이 없어서 RxSwiftCommunity/RxDataSource를 보면서 작성중이다

  1. Start by defining your sections with a struct that conforms to the SectionModelType protocol:
    • define the Item typealias: equal to the type of items that the section will contain
    • declare an items property: of type array of Item

시키는대로 하나씩 해봅시다

1. SectionModelType 프로토콜을 채택하는 section을 표현할 구조체를 만들어라

- Item을 typealias로 선언하고 section에서 사용할 데이터와 = 처리해라
- items 프로퍼티를 선언하자 type은 [Item] 로 해라
import RxDataSources
struct SectionOfAppleProduct {
    var header: String
    var items: [Item]
}

extension SectionOfAppleProduct: SectionModelType {
    typealias Item = Product
    
    init(original: SectionOfAppleProduct, items: [Item]) {
        self = original
        self.items = items
    }
}
  1. Create a dataSource object and pass it your SectionOfCustomData type:

dataSource 오브젝트를 내가만든 SectionType으로 생성해라

기존 tableView 관련 코드는 지워주고,bind함수에서 datasource를 만들자

  1. Customize closures on the dataSource as needed:

필요에따라 dataSource의 클로져를 커스텀

기타 header title도 설정하고, 편집이나 이동도 true로 설정해주자

    func bind(_ viewModel: MainViewModel) {
        let dataSource = RxTableViewSectionedReloadDataSource<SectionOfAppleProduct> { datasource, tableview, indexpath, item in
            let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexpath)
            cell.textLabel?.text = item.name
            cell.detailTextLabel?.text = item.printPrice
            return cell
        }
        
        dataSource.titleForHeaderInSection = {dataSource, index in
            return dataSource.sectionModels[index].header
        }
        
        dataSource.canEditRowAtIndexPath = {dataSource, indexPath in
            return true
        }
        
        dataSource.canMoveRowAtIndexPath = {dataSource, IndexPath in
            return true
        }
    }
  1. Define the actual data as an Observable sequence of CustomData objects and bind it to the tableView

실제 데이터를 옵져버블로 만들어서 테이블뷰와 바인딩 해라

import RxRelay

struct MainViewModel {
    let data = BehaviorRelay<[SectionOfAppleProduct]>.just([
        SectionOfAppleProduct(header: "MacBook", items: [
            Product(name: "MacBook Air", price: 1290000),
            Product(name: "MacBook Pro 14", price: 2690000),
            Product(name: "MacBook Pro 16", price: 3360000),
        ]),
        
        SectionOfAppleProduct(header: "iPad", items: [
            Product(name: "iPad 9th", price: 449000),
            Product(name: "iPad Air", price: 779000),
            Product(name: "iPad Mini", price: 649000),
            Product(name: "iPad Pro 12.9", price: 1379000),
        ]),
        
        SectionOfAppleProduct(header: "iPhone", items: [
            Product(name: "iPhone 13 Mini", price: 950000),
            Product(name: "iPhone 13", price: 1090000),
            Product(name: "iPhone 13 Pro", price: 1350000),
            Product(name: "iPhone 13 Pro Max", price: 1490000),
        ])
    ])
}

bind함수 맨마지막에 작성해줍니다

viewModel.data
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: bag)

겁먹어서 계속 미루고 있었는데, 생각보다 매우쉽다..
그냥 해당 github HOW 따라서 하니까 바로 적용이 된다

응..? 근데 detailLabel에 표시한 가격이 전혀 안나오고 있다(왜지?)
그래서 그냥 CustomCell을 만들어서 거기에 표현해줬다
(과정은 생략, 코드도 cell을 typeCasting해준거 빼고는 거의 동일하다)

아주 잘 나오네 ㅎㅎ
다음에는 삭제, 이동, 추가 를 구현해봐야지 :)

좋은 웹페이지 즐겨찾기