🍎swift

UICollectionViewDelegateFlowLayout 활용 Cell크기 동적 할당

Kendrick 2024. 4. 15. 11:50

cell의 넓이는 고정값으로 줄 수 있지만 높이는 내용에 따라 동적으로 할당 받아야하는데 어떻게 해야할지 뒤적뒤적이다 UICollectionViewDelegateFlowLayout라는 protocol이 있다는 것을 발견

요놈을 활용해 동적 높이를 할당해보자 

공식문서

UICollectionViewDelegateFlowLayout

The methods that let you coordinate with a flow layout object to implement a grid-based layout.

The methods of this protocol define the size of items and the spacing between items in the grid.

공식문서를 요약하면 이렇게 나온다.

즉 아이템과 grid사이의 size와 spacing 조절해주는 protocol이런건데 이걸 보고 딱 감이왔다 아 이놈이네?

여담이지만 공부하다보니 이 프로토콜을 사용하여 셀의 크기, 헤더와 푸터의 크기, 섹션간 간격 등을 지정할 수 있다고 한다.

해결해보자

메서드들의 쓰임세를 정리하면 아래와 같다.

  • 셀 크기 조정: sizeForItemAt 셀의 크기를 지정
  • 섹션 패딩 조정: insetForSectionAt  섹션의 내부 여백을 설정
  • 행 간의 간격 지정: minimumLineSpacingForSectionAt 
  • 아이템 간의 간격 지정: minimumInteritemSpacingForSectionAt

1트

나는 셀 크기를 동적으로 지정해줘야하니 sizeForItemAt를 사용해서 임시로 아래와 같이 만들어줬다.

extension DetailChatViewController: UICollectionViewDelegateFlowLayout {
   func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let message = viewModel.messageRepository.getMessages()[indexPath.row].content
        return CGSize(width: collectionView.frame.width, height: 100)
        }
}

처음 코드를 보고 오 쉽네 ? 그냥 데이터 내용을 height에 넣고 리턴해주면 되는건가라고 생각 할 수 있지만

height 100이 넘어가지 안는 내에서 만 작동을 하게 된다. 

즉 sizeToFit()를 사용할때처럼 크기를  딱 맞춰서 동적인 UI를 적용하고 싶었다.

retrun타입을 보니 CGSize, message를 CGFloat타입으로 만들어버리면 끝이구만

2트

boundingRect(with:options:attributes:context:)라는 메서드는 문자열에 대해 지정된 속성을 사용하여 그 텍스트가 차지할 최소 영역을 계산하고 그 결과를 CGRect로 반환하는 메서드이다. 즉 문자열의 구성을 보고  크기가 얼마인지 정확하게 계산해 retrun을 해주는 메서드, 이것을 활용해 texrtView에서 나오는 text들의 크기를 측정하고 반환하도록 했다.

  1. boundingRect() 를 사용하기 위해 CGSize가 필요하다.
  2. height는 greatestFiniteMagnitude**를이용해서 무한대로 조정
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let message = viewModel.messageRepository.getMessages()[indexPath.row].content
            let maxWidth = collectionView.frame.width - 20
            let textSize = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)

            let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]
            let estimatedFrame = NSString(string: message).boundingRect(with: textSize, options: .usesLineFragmentOrigin, attributes: nil, context: nil)

            return CGSize(width: collectionView.frame.width, height: ceil(estimatedFrame.height) + 20) // 추가 마진
        }

결과 …

작동은 잘 되지만 …..

??? 어마무지한 Autolayout충돌이 되는 모습을 보여준다 ..

3트.. 해결

두번째 시도에서 attributes에 nil을 넣어줬던 것이 실수였다.

UICollectionViewLayout에서 attributes역할은

  1. 폰트 스타일: 텍스트의 폰트 종류와 크기를 지정 예를 들어, **UIFont.systemFont(ofSize: 15)**는 시스템 폰트로 15포인트 크기를 사용
  2. 텍스트 색상: **NSAttributedString.Key.foregroundColor**를 사용하여 텍스트의 색상을 지정
  3. 밑줄: NSAttributedString.Key.underlineStyle을 사용하여 텍스트에 밑줄을 추가
  4. 텍스트 정렬: NSAttributedString.Key.paragraphStyle를 사용하여 텍스트의 정렬(왼쪽, 가운데, 오른쪽)을 설정 가능.
  5. 배경 색상: NSAttributedString.Key.backgroundColor를 사용하여 텍스트의 배경 색상을 지정
  6. 삭제선: NSAttributedString.Key.strikethroughStyle을 사용하여 텍스트에 삭제선을 추가 가능
  7. 커스텀 속성: 개발자가 정의한 커스텀 속성을 텍스트에 적용할 가능

이러한 역할을 갖고 있다고 한다.

<예제코드>

let attributes: [NSAttributedString.Key: Any] = [
    .font: UIFont.boldSystemFont(ofSize: 16),
    .foregroundColor: UIColor.blue,
    .backgroundColor: UIColor.yellow,
    .underlineStyle: NSUnderlineStyle.single.rawValue
]
let attributedString = NSAttributedString(string: "Hello, world!", attributes: attributes)

두번째 코드에서 attributes를 추가해주고 width를 collectionView크기로 맞춰줘 통일감을 주었다..

   
   //두번째 코드 
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
     let message = viewModel.messageRepository.getMessages()[indexPath.row].content
     let maxWidth = collectionView.frame.width - 20
     let textSize = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)
     let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]
     let estimatedFrame = NSString(string: message).boundingRect(with: textSize, options: .usesLineFragmentOrigin, attributes: nil, context: nil)
     return CGSize(width: collectionView.frame.width, height: ceil(estimatedFrame.height) + 20) // 추가 마진
}

//리팩토링 코드 (해결코드)
extension DetailChatViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
          let message = viewModel.messageRepository.getMessages()[indexPath.row].content
          let width = collectionView.frame.width
          let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
          let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)]
          let estimatedFrame = NSString(string: message).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
          return CGSize(width: width, height: estimatedFrame.height + 20)
      }
}

잘 돌아간다 ! 완료 !!