[UITableView] 테이블뷰에서 발생했던 Cell reuse 문제와 해결방법

2021. 8. 23. 20:16UIKit, H.I.G

728x90

아이폰에서 가장 많이 사용되는 뷰가 무엇이냐 묻는다면 TableView와 CollectionView라고 답할 수 있을 것이다.
이 두 개만 잘 배워놔도 대부분의 뷰를 구현할 수 있을 만큼
테이블뷰와 컬렉션뷰는 iOS 개발에서 정말 중요한 부분을 차지한다.

하지만,
초보 iOS개발자라면 누구나 한번쯤 TableView를 배우면서 아래와 같은 버그를 맞닥드린적이 있을 것이다.

테이블뷰에서 스크롤을 하게 되면 제멋대로 셀의 상태가 변해있는 충격적인 상황이 발생하게 되는 이런 상황...

오늘은 TableView에서 이 버그가 왜 발생하고, 어떻게 해결할 수 있는지에 대해 다루어보도록 하겠다.

스위치를 분명 켜놓았는데, 스크롤을 하면 원상복구가 된다

 

1️⃣ 우선, TableView가 어떤 원리로 동작하는지를 정확하게 알아야 한다!


테이블뷰는 세로 스크롤을 할 때 같은 형태가 반복되면서, 내용은 달라지는 형태의 뷰를 만들 때 사용된다.

즉 비슷한 형태의 각 셀을 일일이 만드는 것이 아니라, 큰 틀을 만들어서 안에 들어가는 내용만 바꿔주는 방식으로 
비슷한 형태의 뷰를 일일이 만들지 않고, 그냥 큰 틀만 만들어서 내용만 바꿔주는 느낌이라고 생각하면 될 것 같다 ^_^

tableView는 그림과 같은 원리로 작동한다.

이 원리대로 동작할 수 있게 도와주는 부분이 dequeueReusableCell 메소드이다.

애플 공식 문서에 따르면,
"지정된 재사용 식별자에 대해 재사용 가능한 테이블 보기 셀 개체를 반환하고 테이블에 추가한다."라고 쓰여있다.

이를 쉽게 풀어서 설명해보면,
identifier에는 재사용될 셀(모형틀)을 String 타입으로, indexPath에는 셀의 위치를 지정해 tableView안에 내용을 추가한다는 뜻이다.

dequeueReusableCell에 대한 설명 (출처: developer.apple)

그러다 보니 내용만 바꿔주도록 설정되어있는 라벨은 정상적으로 바뀌지만,
Cell의 상태와 관련된 버튼의 클릭, 스위치의 클릭 여부, 색의 변화 같은 경우에는 위와 같은 버그가 발생하게 되는 것이었다.

 

2️⃣ 그래서, 해결방안은?


해결방안은 간단하다.
라벨과 같은 방식을 적용해주면 된다. (왜냐하면 라벨은 버그가 없었으니깐)

라벨의 내용은 categories라는 상수형 배열을 만들어주고
cell 안에 있는 titleLabel 안에 indexPath.row를 활용해서 위치에 맞게 넣어주는 방식을 사용했었다.

💡 잠깐, indexPath.row란?

indexPath는 tableView의 행을 식별하는 인덱스 경로를 의미한다.

indexPath는 section과 row라는 세부 값으로 다시 나누어지는데,
TableView(CollectionView) 구조를 살펴봤을 때, 큰 덩어리인 section(들)로 우선 나누어지며, 그 section 안에 다시 여러 row로 구성되어 있는 구조인 것을 아래의 그림에서 확인할 수 있다.
즉, 여기서의 row는 반복되는 Cell 1개와 같은 뜻으로 이해하면 되겠다.

다시 돌아가, 결론적으로 그럼 indexPath.row란
테이블 뷰에서 지금 선택한 Cell의 위치를 인덱스로 나타내 주는 것을 의미한다고 볼 수 있다!

tableView는 다음과 같이 구성된다. (feat. SOPT)

label 내용을 배열로 묶어서 정의해주고,
indexPath.row에 따라 내용을 tableViewCell의 순서대로 넣어준 것이 우리가 label 안에 내용을 넣어준 방식이다.

이제 이해가 조금 될 거라고 생각한다...! (나도 이해했는ㄷ..)

label에 들어갈 내용을 배열로 선언해주고, label.text = categories[indexPath.row]를 사용해 데이터를 넣어준다.

즉, 이를 해결하기 위해서 UISwitch나 UIButton도 이런 방식을 사용하면 된다는 것이다.

switch의 상태를 각각 담을 배열을 Bool 형태로 아래와 같이 만들어주고

똑같이 indexPath.row를 활용해서 스위치 상태를 나타낼 수 있도록 전달해주면 된다.

대신, 여기서는 앞선 label과 다르게 우리가 고려해야 할 사항이 하나 더 있다.

정해진 데이터를 그냥 띄우기만 하면 되는 라벨과 다르게,
스위치에서는 사용자가 해당 스위치를 클릭해서 상태가 변화될 경우를 고려해야 한다는 점이다.

"사용자가 클릭 -> UISwitch 상태 변화 -> indexPath에 맞게 배열의 상태를 false/true로 변화" 하는 로직이 추가로 필요하다.

이 부분 구현은 Delegate pattern을 활용할 수 있었다.

cell에는 changed라는 프로토콜을 선언해주고
그 프로토콜을 뷰컨에서 채택해 아래와 같이 구현해줬다.
위에서와 역으로 inOn이라는 상태를 해당 index에 맞게 상태를 바꿔주게 된다.

이때, tableView.reloadData()라는 부분이 사용되는데
이 부분은 말 그대로 현재 tableView의 상태를 리로드(업데이트)하겠다는 것을 의미한다.
스위치의 상태가 바뀌었으니 그것에 맞게 tableView의 상태도 업데이트해줄 필요가 있는 것이다!

그리고 cell에서 버튼 Action을 걸고 changed 함수를 호출하게 되면,
정상적으로 데이터 전달이 이루어질 수 있을 것이다!
(확실히 delegate를 알아두니깐 코드가 이해가 되는구먼..)

오늘은 여기까지✌🏻

이제 정상적으로 작동한다 ><

 

💡 Reference


 

[iOS] Cell을 재사용시 생기는 문제점들과 해결 방법

우리는 테이블 뷰와 컬렉션 뷰를 설계할 때 그냥 셀을 사용하지 않고 재활용 셀(dequeueReusableCell)을 사용합니다. 왜 그냥 셀을 사용하지 않고 재활용 셀을 사용하느냐!?  만약에 우리가 테이블 뷰

lsh424.tistory.com

 

[ UITableView ] indexPath.row와 indexPath.section는 어디서 나온거니?

테이블 뷰의 열 정보와 섹션 정보를 얻으려면 NSIndexPath를 사용하게 됩니다. 하지만 NSIndexPath의 관련 문서를 봐도 indexPath.row와 indexPath.section에 대해서는 한마디 말도 없습니다! 그래서 늘 궁금해

blog.daum.net

728x90