맛동산이

Swift) RxDataSource 사용하는법 정리 본문

앱/Swift

Swift) RxDataSource 사용하는법 정리

진ddang 2024. 2. 25. 17:27

장점

우선 rxDataSource가 나오는 이유는 tableView와 CollectionView의 delegate가 많은 메소드를 생성해야하는 부분에서 불편감이 많기 때문이다.

즉 굳이 tableViewDelegate, collectionViewDelegate의 프로토콜을 채택하여 메소드를 구현해줄 필요가 없어진다.

이전의 rx를 사용한 datasource를 사용하게 되면, 여러 섹션이 존재하거나, 데이터 추가, 수정, 삭제등의 변경사항이 생겨도 reloadData를 기반으로 움직이기 때문에 애니메이션을 사용하기가 굉장히 힘들었다고 한다~

(난 써본적없다. )

이러한 부분을 RxDataSource는 SectionModelType과 AnimatableSectionModelType을 통해서 해결하였다.

이름처럼 애니메이션도 가능하고 , 섹션처리도 가능한 것!

요약

  • DataSource를 사용해서 rx를 사용하면, delegater구현을 하지 않아서 편하다.
  • 근데 Section과 애니메이션 적용이 어려웠는데, RxDataSource는 가능하다!

rxDataSource를 쓰는 방법

  1. Data Model을 생성한다.
  2. Section을 정의하기 위해 SectionModelType을 채택하는 구조체를 생성한다.
  3. Section구조체 타입을 인자로 할당하는 DataSource를 정의한다.
  4. 데이터를 observable sequence로 만든다.
  5. 밑의 4개 메소드를 사용해서 tableView/collectionView에 바인딩한다.
  • rx.items(dataSource:protocol<RxTableViewDataSourceType, UITableViewDataSource>)
  • rx.items(cellIdentifier:String)
  • rx.items(cellIdentifier:String:Cell.Type:_:)
  • rx.items(_:_:)

SectionModelType이란, 모델을 정의하는것이다.

sectionModelType은 original과 items가 존재하는데, original은 section이며, items는 row의 데이터를 의미한다.

실제 사용하는 방법 정리

rxdatasoure를 사용하면서 가장 핵심적인 부분은 SectionModel을 생성하고 SectionItem을 생성하는 부분이다. 근데 이 부분이 굉장히 좀 까다롭고 귀찮은 과정이다..

1. SectionModel

  • 싱글 섹션의 경우
struct CellModel { 
  var title: String
	var description: String
}

struct SectionModel { 
	var items: [Items]
}

extension SectionModel: SectionModelType {
    typealias Item = CellModel
    init(original: SectionModel, items: [Item]) {
        self = original
        self.items = items
    }
}

이렇게 섹션 모델을 만들고 그안에 items라는 배열이 존재, 한다.

간단히 설명하면 sectionModel이라는 타입의 배열을 통해서 우리는 섹션을 받아오고 그 안에 items가 한 섹션에 들어갈 데이터들이 될것이다.

  • 멀티섹션의 경우
struct CellModel { 
  var title: String
	var description: String
}


enum SectionModel {
	case first([SectionItem])
}
enum SectionItem {
	case defaultCell(CellModel)
}
extension SectionModel {
	typealias Item = SectinoItem
	var items: [Item] {
		switch self {
			case .first(let items): return items
		}
	}
	init(originial: SectionModel, items: [SectionItem] {
	switch original {
		case .first: self = .first(items)
		}
	}
}

2. RxDataSource 만들고 binding해주기

lazy var dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(configureCell: { dataSource, tableView, indexPath, item in
	switch items {
		case .defaultCell(let element):
						let cell = tableView.dequeueReusableCell(withIdentifier: "만들어둔 셀", for: indexPath)
            cell.title?.text = item.title
						cell.description?.text = item.title
            return cell
        }
	})
}
override func viewDidLoad() {
        ...
        
        reactor.state
						.map { $0.data }
            .bind(to: collectionView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
    }

위에서 lazy var로 한거는 나의 경우에는 dataSource를 didLoad와 같은 메소드 밖에서 사용하여, 접근이 가능하도록 하였고, 이를 위해서는 변경이 가능해야하는데 초기 값이 없기때문에 lazy라는 키워드를 통해서 해결하였다.

reactorkit이기 때문에 데이터는 reactor의 data에 넣어뒀다.

3. indexPath로 해당 데이터 접근하기

collectionView.rx.itemSelected
	.asDriver()
	.drive(onNext: { [weak self] indexPath in
		guard let self else { return }
		let item = reactor.currentState.data[indexPath.section].items[indexPath.row]
		//collectionView나 tableview에서 섹션이 존재하면, 이렇게 indexPath.section과 row가 존재
		if case let SectionItme.defaultCell(element) = item {
			let vc = ViewController(item.link)
			}
		}
}

이런식으로 접근하게 된다.

4. data를 사용할때

api로 데이터를 불러온것을 사용할때는 항상 item 배열로 파싱해줘야 사용할수 있다.

따라서

response.sucess
	.compactMap { $0.data }
	.map { let sectionItem = $0.content(불러올 데이터).map { SectionItem.defaultCell($0) }
				  let sections = [SectionModel.first(sectionItem)] 
					return sections
			}
	.map { Mutation.updateSection($0) }
반응형