iOS/Swift - CollectionView 새로운 Cell 추가하기

Kim-leo·2024년 3월 3일
0
post-thumbnail

들어가며

안녕하세요. Kim-leo 입니다.

CollectionView 내에 있던 기존의 cell 사이에 새로운 cell을 추가하려는 과정에서 발생한 에러들과 내적 갈등, 그리고 이를 해결한 과정을 담았습니다.

감사합니다 :)

So, what do you want to do?

  • 컬렉션 뷰 내의 첫 번째 셀을 클릭하면, 그 사이에 새로운 셀을 추가할 수 있는 기능을 구현하고 싶었다.
  • UICollectionViewDelegate 내장 함수 중에 셀을 선택했을 때 동작하는 아래 함수가 있는데, 여기 안에 말고! 다른 외부 함수에서 위 기능을 구현하고자 했다.
  • 위 그림에서 보면 + 직접 입력 셀을 누르면 여기에 새로운 셀이 추가되도록 하고 싶었다.

  • 물론 didSelectItemAt 함수 안에 저 기능을 구현하는 게 제일 easy하지만, 저 함수가 엄청 복잡, 비대하고 무거움을 방지하고자 다른 외부 함수를 선언해서 저 친구의 부담을 줄여주고 싶었다.

// collectionView를 쓸 때 밥 먹듯이 많이 봐온 익숙한 그 함수 세트들 중 하나.
func collectionView(_ collectionView: UICollectionView, 
didSelectItemAt indexPath: IndexPath) {}

내적 갈등 발생

아 근데 그러면 함수 간 응집도(Cohesion) 이슈가 발생해버..리진 않을까 하는 걱정이 생겼다.
"소프트웨어 아키텍처 101"의 저자 마크 리처즈, 닐 포드 선생님들은 모델을 설계할 때 모델 간 응집도(Cohesion)과 결합도(Coupling)을 고려하라고 말씀하셨는데...?

한편, 개발자 분들이라면 한 번이라도 들어보거나 추천도서로 찾아본 책인, "클린 코드" 의 저자 로버트 C.마틴 선생님은 함수 챕터에서 아래와 같은 명언을 남기셨으니, 이 말씀을 근거로 계속 나아가보자. (책 광고 아니에요!)

"함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다."
'클린 코드', p44. 로버트 C. 마틴

네 로버트 선생님.

didSelectItemAt 말고 다른 함수 addNewCell 에서 기능 구현하기

func collectionView(_ collectionView: UICollectionView, 
didSelectItemAt indexPath: IndexPath) {
	// (중략...)
}

func addNewCell() {
	// 새로운 셀을 추가하는 기능을 여기다가 만들어보자!
}

Apple Developer Documentation 에서 찾아본 결과,
컬렉션뷰 내에 새로운 셀의 indexPath만 잘 설정하면, 삽입할 수 있는 함수가 존재했다!

func addNewCell() {
	myCollectionView.insertItems(at: [IndexPath(item: 1, section: 0)])
}

What is the problem?

실행 결과, 바로 Error 퉤.

사정을 듣자 하니... 잘못된 Batch 업데이트가 감지됐으며, (셀) Batch 업데이트를 수행하기 전과 후에 데이터 소스가 반환한 섹션 및 항목 수가 업데이트와 일치하지 않았다고 한다.

컬렉션뷰에 바로 셀을 삽입해버리면 문제가 있다는 이야기군요.
그렇다면, 셀 삽입 후에 컬렉션뷰를 업데이트 하면 되려나 추측했다. (해치웠나?)

func addNewCell() {
	myCollectionView.insertItems(at: [IndexPath(item: 1, section: 0)])
    viewFile.lowerCollectinView.reloadData()
}

실행 결과, 어림도 없다, 암!
똑같은 Error 퉤.

한 줄기의 빛. performBatchUpdates

그렇게 stackoverflow와 구글링을 한지 몇 시간 째,

셀을 삽입, 삭제, 다시 로드 또는 이동하거나 하나 이상의 셀과 연관된 레이아웃 매개변수를 변경하는 데 사용할 수 있으며, 매개변수 에 전달된 블록을 사용하여 updates수행하려는 모든 작업을 지정하는 강력한 함수

를 발견했다.

func performBatchUpdates(
    _ updates: (() -> Void)?,
    completion: ((Bool) -> Void)? = nil
)

이 함수 안에 위의 코드 myCollectionView.insertItems(at: [IndexPath(item: 1, section: 0)])를 삽입한다면 어떨까?

func addNewCell() {

	myCollectinView.performBatchUpdates {
            myCollectinView.insertItems(at: [IndexPath(item: 1, section: 0)])
           
           // (중략...)
            
        } completion: { [weak self] _ in
        }
}

실행 결과... 성공.

진짜 해치웠나..?

단, performBatchUpdates 메소드를 사용할 때 주의할 점이 있다.

이 메서드를 호출하기 전에 컬렉션 뷰의 레이아웃이 최신이 아닌 경우 다시 로드가 발생할 수 있다. 문제를 방지하려면 updates블록 내부의 데이터 모델을 업데이트하거나 호출하기 전에 레이아웃이 업데이트되었는지 확인 해야 한다고 한다.

삭제는 일괄 작업에서 삽입 전에 처리되는데, 삭제에 대한 인덱스는 일괄 작업 전에 컬렉션 뷰 상태의 인덱스를 기준으로 처리되고, 삽입에 대한 인덱스는 일괄 작업에서 모든 삭제 후 상태의 인덱스를 기준으로 처리된다.

그 외 관련 메소드는 무엇이 있을까?

컬렉션뷰의 셀 업데이트 관련한 함수가 더 있을까 해서 찾아보았더니,
reloadData() 메소드가 있었다.

이는 컬렉션뷰 뿐만 아니라 테이블뷰에서도 쓸 수 있는데, 컬렉션뷰, 테이블 뷰의 모든 데이터를 reload 하는 기능이다.

즉, 위 메소드가 호출되면, 처음부터 다시 데이터를 가져와서 변화된 셀 사이즈 offset에도 알맞게 컬렉션뷰에 적재한다는 건데,

셀 하나 추가하겠다고 가만히 있던 나머지 데이터 전체를 건드린다라...

어 이거 시간 복잡도(Time Complexity)가 O(n) 이려나? 셀 내 데이터 개수에 따라 쫙 로드하면, 데이터 수에 따라 수행 시간이 선행적으로 증가할 느낌적인 느낌인데..
(혹시 아시는 분 계시면 댓글 부탁드립니다..)

마치며

컬렉션뷰 내에 새로운 셀을 삽입할 때 있어서는,
업데이트를 수행하기 전과 후에 데이터가
반환한 섹션 및 항목 수가 업데이트와 일치해야 한다

는 교훈을 친절한 에러님과 함께 체득했다.

이 포스팅에서 다룬 프로젝트 깃허브 링크

profile
iOS Developer

0개의 댓글