Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

teunteun2

[RxSwift] CollectionView in TableView Cell 만들기 본문

iOS

[RxSwift] CollectionView in TableView Cell 만들기

teunteun2 2022. 4. 18. 22:48
  • RxDataSource Github에 소개된 RxSwift로 TableView(CollectionView) 만드는 4가지 방법
  • RxSwift로 한 종류의 Cell을 가진 CollectionView 만들기
  • RxSwift로 두 종류 이상의 Cell을 가진 TableView 만들기
  • CollectionView in TableView Cell Dispose issue

 

[0] RxSwift로 TableView 만드는 4가지 방법

RxDataSource Github 페이지에 가보면 TableView 를 만드는 방법 4가지를 볼 수 있는데영

  • rx.items(dataSource: ...) ⇒ RxDataSource 이용
  • rx.items(cellIdentifier:String) ⇒ Cell을 xib 파일로 생성했을 때 쓰는걸까요 ? 요부분 누가알려조
  • rx.items(cellIdenfier:String:Cell.Type:_:) ⇒ 한 종류의 Cell을 가진 TableView 만들기만 가능
  • rxitems(::) ⇒ 여러 종류의 Cell을 가진 TableView 만들기 가능

제가 RxDataSource 공부를 못한 관계로 ...

세번째 네번째 방법을 알아보도록 합시다


 

[1] RxSwift로 한 종류의 Cell을 가진 CollectionView

어차피 TableView && CollectionView 똑같으니까

이번 과제에서 한 종류의 Cell만 필요했던 CollectionView를 예시로 들게요 ?

우선 어떤 방법으로 TableView를 생성하든 제일 먼저 해야하는 것은

Cell에 넣어줄 Data를 Observable로 만들어주는 것 !

Observable.of(StoryModel.sampleData)
// 간단하게 of로 만들어주었어요

1. TableView Cell에 넣어줄 Data와 CollectionView를 bind

이 때 ! bind 란 ?

원래는 subscribe를 통해 Observable을 구독해서 방출되는 이벤트를 요리조리 굴릴 수 있었는데여

(값 대입하는 코드도 직접 써줘야 했음)

exampleObservable.subscribe {
					self.textfield.text = $0
					}
					.disposed(by: disposeBag)

bind는 Observable과 UI를 그냥 한 몸으로 묶어버린다고 생각하면 됩니다.

Observable 에서 emit 되는 것들이 bind 해준 UI로 바로 연결되는 것이죠

exampleObservable.bind(to: textfield.rx.text)
					.disposed(by: disposeBag)

한가지 Cell로만 CollectionView를 구성하고 싶을 땐 rx.items(cellIdentifier: String, cellType: Cell.Type)로 Cell을 명시해줍니다.

Observable.of(StoryModel.sampleData).bind(to: storyCollectionView.rx.items(
            cellIdentifier: StoryCollectionViewCell.identifier,
						cellType: StoryCollectionViewCell.self))

2. 클로저를 통해 row, element, cell 값으로 cell 구성해주기

bind으로 데이터와 컬렉션뷰를 묶고 Cell까지 명시해주었다면 클로저를 통해

순서대로 (row, element, cell) 값을 가져올 수 있습니다

  • row : collectionView cell 몇번째인지 (익숙하져?)
  • element: 묶어준 데이터의 element
  • cell: 묶어준 collectionView의 cell
Observable.of(StoryModel.sampleData).bind(to: storyCollectionView.rx.items(
            cellIdentifier: StoryCollectionViewCell.identifier,
						cellType: StoryCollectionViewCell.self)) { (row, element, cell) in
                
                // cell content 넣어주기 ex cell.profileImage = UIImage(named: ..
            }
            .disposed(by: disposeBag)

 

[2] RxSwift로 여러 종류의 Cell을 가진 TableView

이것도 bind 해주는 것까진 똑같습니다.

하지만 요번엔 items 뒤에 괄호열고 어떤 Cell인지 명시해주지 않아요

Observable.of(PostModel.sampleData).bind(to: mainView.feedTableView.rx.items)

이 방법은 자동완성이 된답니다.

  1. 위 코드까지 친 다음 ) 옆에 .을 누르면 자동완성이 떠요
  2. 자동완성 엔터
  3. 1번에서 썼던 .을 지우세요
  4. (UITableView, Int, PostModel) → UITableViewCell 파란 부분 엔터 치면 자동완성 끝

이번엔 클로저의 세가지 매개변수가 약간 다릅니다

한가지 종류의 cell 을 가지고 만들 땐 (row, element, cell) 였다면,

이번엔 (tableView, row, element) 입니다 cell을 tableView 매개변수를 가지고 만들어주어야 해요

Observable.of(PostModel.sampleData).bind(to: mainView.feedTableView.rx.items) {
 (tableView, row, element) -> UITableViewCell in
        
    switch row {
    case 0:
        guard let cell = tableView.dequeueReusableCell(
					withIdentifier: StoryCollectionTableViewCell.identifier,
					for: IndexPath.init(row: row, section: 0)) as? StoryCollectionTableViewCell else { return UITableViewCell() }
        
        return cell
    default:
        guard let cell = tableView.dequeueReusableCell(
					withIdentifier: PostTableViewCell.identifier,
					for: IndexPath.init(row: row, section: 0)) as? PostTableViewCell else { return UITableViewCell() }

        return cell
    }
}
.disposed(by: disposeBag)

 

[3] Cell의 Disposable 처리

예전에 습관성 .disposed(by: disposeBag) 쓰다가 발견한 issue

cell 내의 버튼을 .red 에서 .blue로 바꾼 후 스크롤을 내렸더니 , 재사용된 cell의 버튼이 .blue로 되어있다

[해결방법1] 그냥 prepareForReuse에서 색을 초기화 시켜주자

그냥 cell이 재사용되면서 호출되는 prepareForReuse()에서 색을 다시 초기화 시켜도 되지만

Rx로 TableView Cell를 묶어주었다는 것을 잊으면 안된다

[해결방법2] disposeBag 초기화

TableView 와 CollectionView는 Cell을 계속해서 재사용하기 때문에

계속 스크롤을 하면 completed 상태로 넘어가지 않기 때문에 계속해서 disposable이 쌓인다.

그래서 prepareForReuse()에서 disposeBag을 새로 교체해주었다.

(화면 밖에서 사라질 때 교체해주어도 됨 → delegate 의 didEndDisplaying)

override func prepareForReuse() {
        super.prepareForReuse()
        
        disposeBag = DisposeBag()
    }

 

[4] CollectionView in TableView Cell 하면서 생긴 이슈

위 경험으로 3주차 과제를 하면서 똑같이 모든 TableView Cell과 CollectionView Cell을 처리해주었다.

근데 CollectionView가 포함되어 있는 TableView Cell을 disposeBag 초기화 해주니 CollectionView 바인딩이 풀린다.

 

해결

우선은 .. 바인딩 함수를 disposeBag 초기화 다음에 다시 호출해주는 방식으로 해결했는데

이게 맞는지 모르겠어요 .. 집단지성 헬프미

override func prepareForReuse() {
        super.prepareForReuse()
        
        disposeBag = DisposeBag()
        // 1. collectionView.reload() 안됨. 그냥 디큐 바인딩이 풀린듯
        // 2. 결국 binding 함수를 다시 호출해주었다. (init은 최초에만 .. 근데 이게 맞아?
        binding()
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        binding()
        constraints()
    }
    
    func binding() {
        
        Observable.of(StoryModel.sampleData).bind(to: storyCollectionView.rx.items(
            cellIdentifier: StoryCollectionViewCell.identifier, cellType: StoryCollectionViewCell.self)) { (row, element, cell) in
                
                cell.profileImageView.image = UIImage(named: element.profileImageName)
                cell.nameLabel.text = element.name
            }
            .disposed(by: disposeBag)
        
    }