RxSwift 사용해서 TableView, CollectionView 구현하기

오늘은 가장 많이 사용하고 기본이 되는 TableView, CollectionView를 이전에 Delegate , DataSource를 사용해서 구현하는 것이 아닌 RxSwift를 사용해서 구현하는 것을 해보았다 ㅎㅎ

 

이전에 RxSwift를 사용하지 않고 구현하던 방식과 RxSwift를 사용하는 방식을 비교하면서 구현해보니 슬슬 RxSwift를 활용해서 구현하는 방식이 감이 오는 것 같다 :)

 

하나하나 살펴보자!

 

TableVIew

먼저 테이블 뷰를 구성할 더미 데이터를 만들어 주었다.

struct Product {
    let imageName: String
    let title: String
}

그 다음 데이터를 보내 줄 ViewModel를 구현한다.

struct ProductViewModel {
    var items = PublishSubject<[Product]>()
    
    func fetchItem() {
        let products = [
            Product(imageName: "house", title: "Home1"),
            Product(imageName: "gear", title: "Home2"),
            Product(imageName: "person.circle", title: "Home3"),
            Product(imageName: "airplane", title: "Home4"),
            Product(imageName: "bell", title: "Home5")
        ]
        
        items.onNext(products)
        items.onCompleted()
    }
}

items를 PublishSubject를 가지도록 구현한다 그 이후 onNext를 통해 만들어 둔 더미 데이터를 전달할 수 있도록 하고 onCompleted를 통해 해당 내용을 완료하는 이벤트를 발생시킨다.

class ViewController: UIViewController {
    let tableView: UITableView = {
       let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return table
    }()
    
    let viewModel = ProductViewModel()
    let disposeBag = DisposeBag()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(tableView)
        tableView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
        fetchTableView()
    }
    
    func bindTableData() {
        viewModel.items.bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { row, product, cell in
            cell.textLabel?.text = product.title
            cell.imageView?.image = UIImage(systemName: product.imageName)
        }.disposed(by: disposeBag)
        
        tableView.rx.modelSelected(Product.self).bind { product in
            print(product.title)
        }.disposed(by: disposeBag)
        
        viewModel.fetchItem()
    }
}

그 다음 VC를 통해 뷰모델의 값을 가질 수 있도록 만들고 disposeBag도 담아서 바로 사용할 수 있도록 구현해 주었다.

그 이후 tableview를 선언해주고 오토레이아웃을 잡아주는 과정은 생략하도록 하겠다 🙂

 

중요한 부분은 바로

func bindTableData() {
  viewModel.items.bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { row, product, cell in
      cell.textLabel?.text = product.title
      cell.imageView?.image = UIImage(systemName: product.imageName)
  }.disposed(by: disposeBag)
  
  tableView.rx.modelSelected(Product.self).bind { product in
      print(product.title)
  }.disposed(by: disposeBag)
  
  viewModel.fetchItem()
}

이전에 RxSwift를 사용하지 않는다면 Delegate, DataSource를 활용해 셀에 데이터 갯수를 설정하고 데이터를 넣어주는 과정으로 구현했다면

 

RxSwift를 통해 반응형으로 업데이트 내용을 바로바로 넣어줄 수 있도록 구현할 수 있다.

위 내용은 3가지가 구현되어있는데 첫번째로 데이터를 셀에 넣어주는 로직과 셀을 선택했을때 나오는 이벤트 로직, 그리고 데이터를 패치하는 메서드를 넣어 구현하였다.

 

이 과정을 통해 RxSwift에서 .bind .modelSelected를 이해하고 사용할 수 있을 것 같다.

 

CollectionView

이전 과정은 TableView와 동일하니 생략하기로 한다 ㅎㅎ 사실 앞에 RxSwift를 적용하는 과정도 동일하게 진행되지만 그래도 기록을 남겨보도록 하자

import UIKit
import RxSwift
import RxCocoa

class ItemViewController: UIViewController {
    
    let viewModel = ItemsViewModel()
    let disposeBag = DisposeBag()
    
    let collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: 100, height: 100)
        layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
        layout.minimumInteritemSpacing = 16
        layout.minimumLineSpacing = 16
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier: ItemCollectionViewCell.identifier)
        return collectionView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupUI()
        fetchCollectionView()
    }
    
    func setupUI() {
        view.addSubview(collectionView)
        
        collectionView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }
    
    func bindCollectionData() {
        viewModel.item.bind(to: collectionView.rx.items(cellIdentifier: ItemCollectionViewCell.identifier, cellType: ItemCollectionViewCell.self)) { row, item, cell in
            cell.titleLabel.text = item.title
            cell.imageView.image = UIImage(systemName: item.imageName)
        }.disposed(by: disposeBag)
        
        collectionView.rx.modelSelected(Items.self).bind { item in
            print(item.title)
        }.disposed(by: disposeBag)
        
        viewModel.fetchItem()
    }
    
}

여기서 테이블 뷰와 다른점은 FlowLayout을 적용한 부분 정도? 다시 한번 코드를 확인해보면서 데이터를 바인딩하는 부분에 익숙해지고 자연스럽게 RxSwift를 사용할 수 있도록 연습 또 연습하자!