UICollectionView의 isSelected 프로퍼티
UICollectionvViewCell에는 isSelected라는 내장 프로퍼티가 있다.
이름에서 알 수 있듯, 해당 셀이 선택되었는지를 나타내는 프로퍼티다.
isSelected는 UICollectionView delegate의 didSelectItemAt, didDeselectItemAt과 함께 불리기 때문에, 이런 식으로 셀이 선택되거나 선택이 해제되었을 때 setSelected와 같은 핸들러 메서드를 만들어 액션을 정의해 줄 수도 있다.
override var isSelected: Bool {
didSet {
setSelected(isSelected)
}
}
isSelected 프로퍼티는 직접 그 값을 변경하면 안 된다
위 isSelected 문서를 보면 직접 그 값을 변경하지 말라고 되어 있다.
" You typically don’t set the value of this property directly. "
직접 값을 변경하면 어떻게 되기에 변경하지 말라고 하는 걸까?
isSelected 프로퍼티를 직접 변경했을 때 생길 수 있는 일
발생한 문제
공식 문서를 읽지 않은 채로, 그냥 코드를 쓰다가 override 할 수 있는 프로퍼티가 있기에 냅다 사용해버린 나는 의도치 않은 사이드 이펙트를 만들어냈다.
아래는 내가 썼던 코드다.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let prevIndex = selectedPhotoIndex,
let prevCell = collectionView.cellForItem(at: prevIndex) as? PhotoCollectionViewCell {
prevCell.isSelected = false
}
guard let selectedCell = collectionView.cellForItem(at: indexPath) as? PhotoCollectionViewCell else { return }
selectedCell.isSelected = true
selectedPhotoIndex = indexPath.item
}
didSelectItemAt에서 전에 선택되어 있었던 cell은 isSelected 프로퍼티를 false로 바꿔주고,
이번에 선택된 cell의 isSelected 프로퍼티는 true로 바꿔주고 있다.
추가로, 특정 액션이 발생하면 (특정 모달을 dismiss하면) isSelected를 false로 바꿔주는 로직도 있었다.
private func deselectPhoto() {
guard let index = selectedPhotoIndex,
let cell = photoCollectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return }
cell.isSelected = false
}
얼핏 보기엔 잘 동작하는 것 같았는데...
cell을 선택하고 해제한 후, 셀이 선택되어 있지 않은 상황에서 컬렉션뷰 배경을 선택하면 다시 그 전 cell이 선택되는 버그가 생겼다.
cell을 아무것도 선택하지 않아도 그 전 셀이 마치 buffer처럼 남아 다시 선택되는 이슈였다.
원인 파악
브레이크 포인트를 찍어보니 cell.isSelected 설정하는 부분은 전혀 불리지 않았지만,
isSelected의 didSet은 불리고 있었다.
내가 의도하지 않은 곳에서 isSelected가 계속해서 다시 set 되고 있다는 뜻이었다.
계속해서 실행시켜보다가 발견한 하나의 패턴이 있는데,
isSelected가 true가 될 때 didSet은 항상 두 번씩 불리고 있었다.
이게 뭔가.. 내부적으로 isSelected를 true로 바꿔주고 있는데 내가 임의로 isSelected를 다시 한 번 true로 변경하고 있는 것 같았다.
그래서 didSelectedItemAt 메서드에 내가 임의로 정의해놓은 isSelected = true 코드를 삭제해보았다.
놀랍게도 그래도 isSelected 값은 true로 바뀌고 있었다. (내가 적은 cell.isSelected = true 코드는 없는데도 선택 타이밍에 맞춰 값이 변경되었다)
이 시점에 큰 혼란이 와 공식문서를 찾아보기 시작했다. ㅋㅋ
아니나 다를까 isSelected 값을 직접 변경하지 말고, 대신 메서드를 사용하라는 이야기를 읽고 해결할 수 있겠다는 모종의 확신이 생겼다.
해결방법
우선 내가 임의로 정의한 cell.isSelected = true / false 를 모두 삭제했다.
그러고 selected 처리가 필요한 모든 곳은 didSelectItemAt, didDeselectItemAt 메서드를 이용해 처리해주었다.
코드는 이렇게 바뀌었다.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let selectedCell = collectionView.cellForItem(at: indexPath) as? PhotoCollectionViewCell else { return }
// cell.isSelect = true로 변경하는 부분을 삭제했다.
selectedPhotoIndex = indexPath.item
}
private func deselectPhoto() {
guard let index = selectedPhotoIndex else { return }
// cell.isSelect = false로 변경하는 부분을 삭제하고 대신 deselectItem()으로 대체했다.
photoCollectionView.deselectItem(at: IndexPath(item: index, section: 0), animated: false)
selectedPhotoIndex = nil
}
코드 내에 isSelect를 직접 참조해 값을 변경하는 부분은 없지만, cell의 isSelect 값은 올바르게 set되고 있다.
UICollectionViewCell의 isSelect는 이렇게 써야 한다.
'iOS > UIKit' 카테고리의 다른 글
iOS 앱 키워드 버블 차트 개발기 (Swift로 버블 차트 구현하기) (1) | 2023.05.11 |
---|---|
iOS 개발 swift로 연월(년, 월)만 설정할 수 있는 UIPickerView 커스텀 하기 (0) | 2023.04.08 |
Swift로 인스타그램 스토리 하트 파티클 애니메이션 만들기 (0) | 2023.04.08 |