개발/Swift

Collection View가 들어있는 동적 높이의 Table view Cell 구현하기

덤벨로퍼 2023. 6. 18. 17:48

https://inf.run/3E93

 

[iOS] Swift Modern Collection View & MVVM 패턴 가이드 강의 | 덤벨로퍼 - 인프런

덤벨로퍼 | , Swift iOS UI, 제대로 다루는 핵심 기술! 📲 iOS Swift 레이아웃 구현을 위한 MVVM 패턴 + 라이브러리 활용 [사진] 사진과 같은 앱의 레이아웃은 어떻게 구현할까요? 스크롤을 내리면 더 다

www.inflearn.com

 

Table view cell 안에 Collection view를 넣어 Grid나 Horizontal 형식의 리스트를 구현해야 할 때가 있다.

기본적으로 Collection view Compositional layout 을 써도 되지만 

회사에서 신기술을 쓰기 어려울 상황도 있기에...

 

이런 상황에 table view cell에 리스트 뿐만 아니라 하당 셀을 접고 펴는등의 액션을 구현해야 할때

결국 상황에 따라 높이가 변하는 Dynamic Height 는 피할수 없다.

 

Cell 을 그릴때 제약조건을 변경해서 높이를 지정해주기

접고 펴는 상태 값, 리스트에 들어가야할 데이터들 등등은 cellForRowAt이나 Rx에서 데이터 바인딩할때

(VC에서 cell을 리턴하는 시점)

Cell에 특정 함수를 통해 넣어주고 적용시키게 된다.

 

func apply(cellData: CellData) {
       
       ///데이터 적용
    }

 

 

그럼 이때 데이터를 받아오기 때문에 데이터를 넣어주고 

TableViewCell의 제약조건 높이를 지정해주면 되지 않을까?

func apply(cellData: CellData) {
        guard case let .birthday(isExpand) = cellData  else {
            return
        }
        if isExpand {
            collectionView.snp.remakeConstraints { make in
                make.top.equalTo(button.snp.bottom)
                make.bottom.equalToSuperview()
                make.leading.trailing.equalToSuperview()
                make.height.equalTo(100)
            }
        } else {
            collectionView.snp.remakeConstraints { make in
                make.top.equalTo(button.snp.bottom)
                make.bottom.equalToSuperview()
                make.leading.trailing.equalToSuperview()
                make.height.equalTo(0)
            }
        }
    }

cellData 에서 확장상태 값을 받아와서 높이를 지정해줬다.

이런경우 동작은 잘 한다.

확장 축소 둘다 동작은 하나 축소 상태에서 확장할때

제약조건 충돌 콘솔에러가 발생한다.

기존 높이 Cell의 높이가 33 인데 그 안에 Colletionview의 높이를 100으로 지정해주려 하기 떄문이다.

Table view 의 HeightForRowAt 델리게이트 함수를 써도 마찬가지 였다.

 

 

Collection view의 높이를 지정해주지 않고 Collection view Cell에 데이터를 넣어주기

만약 동적 높이를 사용하기 위해 높이를 지정해 주지 않고 그 안에 데이터를 계속 넣어줘서 cell의 갯수가 늘면

자연스럽게 높이가 높아지지 않을까?

var cellData: [String] = [] {
        didSet {
            collectionView.reloadData()
				}
}

//cell 에 데이터 적용하는 함수(data)
func apply(cellData: CellData) {
	//cellData를 업데이트 해주면 자동으로 reload Collection view
	self.cellData.append(contentsOf: data)
}

높이를 지정해주지 않은 Collection view (데이터 들어오기전 0) 에 reload를 해도 Cell이 보여지지 않았다

하지만 cell의 크기, cell의 갯수를 지정하는,cell 을 리턴해주는 

collection view delegate 함수들은 돌고있는것이 확인이 되었다.

 

Collection View 커스텀하기

collection view의 사이즈를 재 조정하기 위해

Collection view 를 커스텀 해야한다.

 

기본적으로 collection view에 데이터를 넣어주고 reloadData() 

해주게 되면 instrinsic Content size를 사용해서 높이를 지정해 그려주는데

이때 문제는 데이터가 넣어져도 instrinsic Content size가 0을 리턴한다.

 

class DynamicHeightCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if !(__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize)){
           
            self.invalidateIntrinsicContentSize()
        }
    }
    override var intrinsicContentSize: CGSize {
        return contentSize
    }
}

ㅇ위 로직에서 layoutSubviews를 보면

bound size 와 intrinsicContentSize 를 비교하는데 이때

 

collection view의 intrinsicContentSize 가 들어가야할 사이즈와 다르면

invalidateIntrinsicContentSize() 를 호출하는데 이 함수는

intrinsicContentSize 지금 사이즈를 무효화 하고 다시 계산하여 조정한다.

 

그래서 collection view가 reload 될때 (데이터가 추가, 제거 등등)

collection view layoutSubview 가 호출되고 여기서 비교후에

contentSize 를 재계산하도록 시키는것이다.

 

근데 재조정할때 TableView cell 의 레이아웃을 다시 잡지 않으면 문제가 생긴다.

collection view의 높이가 늘어나도 table cell의 높이가 그대로가 되어버리기 떄문이다.

var cellData: [String] = [] {
        didSet {
            collectionView.reloadData()
            layoutIfNeeded()
    }
}

이때 reload 후에 layoutIfneed 호출하면

위에서 비교하기 위해 사용했던 bounds.size 도 변하기에 알맞게 비교 할수도 있다.

자동적으로 호출 된다.