⚠️ 문제: UICollectionView ListCell 선택 시 여러 셀의 데이터가 출력

셀의 별 버튼 클릭시 해당 셀의 데이터(환율 코드)가 출력되기를 기대하였습니다.
그러나 스크롤 후 다른 셀에서 버튼 클릭 시 선택된 셀 외에도 다른 셀의 정보가 출력되는 현상이 발생하였습니다.
위 사진에서는, 모로코(MAD) 셀을 선택하였으나 다른 셀의 데이터인 XOF가 출력되었습니다.
❗️ 원인: 별 버튼의 Action 설정 시점에 따른 중복 캡처 발생
별 버튼을 눌렀을 때의 액션을 어떻게 할당해주어야할까 고민하다 뷰 컨트롤러에서 동작을 정의하는 것으로 결정했습니다.
기존에 MVC 패턴을 사용하다 MVVM 패턴으로 리팩토링했기 때문에 View 카테고리 내부에서도 View와 ViewController가 나뉘어 있는 상황이었습니다.
이미 View는 UI를 보여주는 역할, VC는 UI의 동작을 정의하고 UI를 변경하는 역할로 나누어져있으므로 해당 역할을 유지하고 싶어 별 버튼의 동작을 ListCell이 아닌 ViewController에서 정의를 하였습니다.
private func makeCollectionViewDiffableDataSource(_ collectionView: UICollectionView) -> UICollectionViewDiffableDataSource<Section, Rate> {
let listCellRegistration = UICollectionView.CellRegistration<ListViewCell, Rate> { [weak self] cell, indexPath, rate in
...
// 셀 설정
let value = self.viewModel.fetchValueStringData(of: rate)
cell.configure(rate, value: value)
// 액션 정의
let action = UIAction { [weak self] _ in
print("selected: \(rate.currencyCode)")
}
// 별 버튼에 액션 추가
cell.starButton.addAction(action, for: .touchUpInside)
}
...
}
따라서 위와 같이 셀의 등록 시점에 버튼의 동작을 정의하고 할당하도록 코드를 작성하였습니다.
그러나 한 가지 간과하고 있던 점이 있었습니다.
UICollectionView는 셀을 재사용한다는 것입니다.
즉, CellRegistration은 셀을 재사용할 때마다 호출되는데, 해당 동작 안에서 cell.starButton.addAction을 하고 있으니
재사용할 때마다 버튼의 액션이 추가되며 self를 캡처하고 있었던 것입니다.
첫 번째 셀을 1번 선택하고 스크롤하여 다시 다른 데이터가 나온 첫 번째 셀을 선택했을 때,
셀이 재사용되었으므로 해당 셀의 별 버튼에 추가된 액션(print문 실행)이 2개가 됩니다.
그러나 각 액션에서 캡처하고 있는 self는 서로 다르기때문에 가리키고 있는 데이터는 동일하지 않습니다.
따라서 print문은 2번 실행되지만 표시되는 데이터는 달라지게 됩니다.
✅ 해결: addAction 시점의 변경 및 클로저 사용
별 버튼의 액션 할당 시점을 셀의 초기화 시점으로 바꾸어주었습니다.
class ListViewCell: UICollectionViewListCell {
...
private var iconButtonSelected: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
...
setButtonAction() // 버튼 액션 설정
}
func setButtonAction() {
let selected = UIAction { [weak self] _ in
self?.starButton.isSelected.toggle()
self?.starButtonSelected?() // 버튼 동작 클로저를 실행
}
starButton.addAction(selected, for: .touchUpInside)
}
}
대신 셀에 버튼의 동작을 정의하는 클로저를 생성하여 VC에서 동작을 정의할 수 있도록 하였습니다.
class ListViewCell: UICollectionViewListCell {
func configure(_ rate: Rate, value: String, icon: String, action: @escaping (() -> Void)) {
...
starButtonSelected = action // 클로저 할당
}
}
extension ViewController {
private func makeCollectionViewDiffableDataSource(_ collectionView: UICollectionView) -> UICollectionViewDiffableDataSource<Section, Rate> {
let listCellRegistration = UICollectionView.CellRegistration<ListViewCell, Rate> { [weak self] cell, indexPath, rate in
guard let self else { return }
// 셀 설정
let (value, icon) = self.viewModel.fetchValueStringData(of: rate)
// 마지막 아규먼트로 버튼의 동작 정의
cell.configure(rate, value: value, icon: icon) { [weak self] in
let bookMarked = cell.starButton.isSelected
self?.viewModel.updateBookMark(of: rate, to: bookMarked)
}
}
...
}
}
'내일배움캠프 > 환율 계산기 - 개인 프로젝트' 카테고리의 다른 글
| [의사결정기록] MVC 패턴에서의 amountTextField 동작 정의 (0) | 2026.03.09 |
|---|---|
| [트러블 슈팅] CoreData에 저장한 데이터가 없는데 데이터가 존재하는 문제 (0) | 2026.03.02 |
| [트러블 슈팅] UICollectionView Cell Autolayout 충돌 (0) | 2026.03.02 |
| [트러블 슈팅] UISearchBar backgroundColor 미적용 (0) | 2026.02.22 |
| [프로젝트 소개] 환율 계산기 앱 만들기 (0) | 2026.02.22 |