
요즘 블로그 글을 아무것도 작성하지 못했었죠...?
회사 일이 너무 바빴어서 집에 오면 바로 잠에 들어야만 했어성 ㅠㅠ
주말에도 일을 했어서 이제서라도...! 글을 다시 작성하게 되었네요
회사일을 하면서 컬렉션뷰 내부의 뷰에 따라 컬렉션뷰의 높이가 스스로
계산되어야 하는 경우가 있었어요
이를 어떻게 하면 좋을까 하다 FlowLayout으로 해당 뷰의 높이를 조절했었는데
Compositional Layout 으로 똑같이 해결이 가능하지 않을까?
라는 생각을 시작으로 해당 블로그의 글을 시작하겠습니다.
private let collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: HomeCollectionViewLayouts.makeHomeTopLayout()
).then {
$0.register(YogiHomeTopCollectionViewCell.self, forCellWithReuseIdentifier: YogiHomeTopCollectionViewCell.reuseIdentifier)
$0.isScrollEnabled = false
$0.layer.masksToBounds = true
$0.layer.cornerRadius = 12
$0.backgroundColor = .lightGray
}
일단 위와 같이 컬렉션뷰를 세팅하였어요
회사 코드는 공개 하면 않되니까
야놀자 UI 와 비슷하게 구성해 보려고 해요!

위 사진과 같이 컴포지셔널 레이아웃을 구성해 보려고 합니다.
final class YogiHomeTopCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier: String = "YogiHomeTopCollectionViewCell"
private let imageView = UIImageView().then {
$0.contentMode = .scaleAspectFit
}
private let titleLabel = UILabel().then {
$0.font = UIFont.systemFont(ofSize: 12, weight: .light)
$0.textColor = .black
$0.numberOfLines = 1
$0.textAlignment = .center
}
override init(frame: CGRect) {
super.init(frame: frame)
setUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setUI() {
contentView.addSubview(imageView)
contentView.addSubview(titleLabel)
imageView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.width.equalToSuperview().multipliedBy(0.6)
make.height.equalTo(imageView.snp.width)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(imageView.snp.bottom).offset(4)
make.horizontalEdges.bottom.equalToSuperview()
make.bottom.equalToSuperview()
}
}
func setData(imageName: String, title: String) {
imageView.image = UIImage(named: imageName)
titleLabel.text = title
}
}
셀 은 위와같이 간단하게 작성해 두었어요
스크롤 허용 여부에 따라 된다 안된다 하는 글들이 있는데
허용을 하더라도 높이가 잡히긴 합니다.
하지만 스크롤이 되는데 저런 경우는 없을것 같아서 막아두었어요
/// Home 상단 에서 사용할 레이아웃
/// - Returns: UICollectionViewCompositionalLayout
static func makeHomeTopLayout() -> UICollectionViewCompositionalLayout {
// 4개의 아이템이 있고
// 4개가 3줄 -> 4분의 1
// 하드하게(.absolute) 할수도 있고 알아서 잡게끔 할수도 있어요 후자로 할게요. (.estimated, fractionalWidth)
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1 / 4),
heightDimension: .fractionalHeight(1)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let hGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(80)
)
let hGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: hGroupSize,
subitems: [item]
)
let vGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(240) // 3줄
)
let verticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: vGroupSize, subitems: [hGroup])
let section = NSCollectionLayoutSection(group: verticalGroup)
section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
아이템 사이즈 부터 설명을 해보면
위 사진처럼 아이템이 각 4개씩 여러줄 등장하는 구조에요
그래서 itemSize 같은 경우 1 / 4 를 하여 4개의 뷰를 구성하겠다 라는 코드이구요hGroupSize 같은 경우 결국 4개를 감싸는 하나의 그룹을 구성하였다고 생각하시면 되요
vGroupSize 는 이들이 여러줄로 나와야하기에 또한번 감싼 그룹을 구성하였습니다.
SwiftUI를 하신분들은 HStack, VStack과 비슷하게 생각해 주시면 이해가 빠를 것 같아요마지막으로 이를 하나의 섹션으로 구성함을 레이아웃으로 반환하였다고 생각하시면 됩니다.
테스트를 위해서 아래 코드처럼 4초뒤에 8개의 데이터로 변경하는 코드를 작성했어요
DispatchQueue.main.asyncAfter(deadline: .now() + 4) { [weak self] in
guard let self else { return }
serviceItemStream.accept( HomeServiceItems.allCases.suffix(8) )
}
현재 상태에서 해당 코드가 실행되더라도 높이의 변화가 일어나지 않을거에요
결국 내부 컨첸츠의 크기를 알아야 하는데 이를
private func updateCollectionViewHeight() {
collectionView.layoutIfNeeded() // 레이아웃 요청
collectionView.invalidateIntrinsicContentSize() // 내부 컨첸츠 사이즈 계산
collectionView.snp.updateConstraints { make in
make.height.equalTo(collectionView.collectionViewLayout.collectionViewContentSize.height)
}
}
위와같이 코드를 구성하여 데이터 변화가 발생하게 될때 해당 코드를 실행하도록 수정합니다.
그러면 WrapContent 처럼 내부 셀의 크기에 따를 컬렉션뷰 높이를 조절할 수 있어요.

생각보다 간단하죠?
그렇다면 테이블뷰에선 못할까요? 아니요..!
class ContentWrappingTableView: UITableView {
override var intrinsicContentSize: CGSize {
return self.contentSize
}
override var contentSize: CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
}
해당하는 것처럼 테이블뷰를 서브클래싱하여
알아서 높이를 잡을 수 있도록 할 수 있어요!
너무 오랜만에 블로그 써서 글쓰는 감각을 잃어버린 것 같아요...! ㅎㅎ
다음 내용은 멀티 모듈에 대해서 글을 작성해 보려고 합니다.
그리고 그걔념을 가지고 Tuist를 작성해 보려고 하니 기대해 주시면 좋을것 같아요
참고로..! 이번 컨텐츠는 FlowLayout도 가능한 방법이에요
다음에 뵈용 안녕