๐Ÿ“ฑiOS

Dynamic Height Cell ๋งŒ๋“ค๊ธฐ - CollectionView + CompositionalLayout

akwlak 2022. 11. 30. 23:34

์ตœ๊ทผ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ Dynamic ํ•˜๊ฒŒ Cell์˜ ๋†’์ด๋ฅผ ๋ฐ”๊พธ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ฒผ๋Š”๋ฐ,

 

ํ•ด๊ฒฐํ•˜๊ธฐ๊นŒ์ง€ ์‚ฝ์งˆ์„ ๋งŽ์ด ํ•œ ๊ฒƒ์— ๋น„ํ•ด ํ•ด๊ฒฐ๋ฒ•์ด ๊ฐ„๋‹จํ•ด์„œ ๊ธฐ๋ก์œผ๋กœ ๋‚จ๊ฒจ๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

 

CollectionView ๋งŒ๋“ค๊ธฐ

 

์ผ๋‹จ ์ด๋ฒˆ๊ธ€์—์„œ๋Š” Compositional Layout์„ ์‚ฌ์šฉํ•ด์„œ CollectionView๋ฅผ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค.

 

Layout ๋งŒ๋“ค๊ธฐ

func createLayout() -> UICollectionViewCompositionalLayout {
    
    let item = NSCollectionLayoutItem(
        layoutSize: .init(
            widthDimension: .fractionalWidth(1),
            heightDimension: .estimated(1)
        )
    )
    
    let group = NSCollectionLayoutGroup.vertical(
        layoutSize: .init(
            widthDimension: .fractionalWidth(1),
            heightDimension: .estimated(1)
        ), subitems: [item]
    )
    
    let section = NSCollectionLayoutSection(group: group)
    
    section.interGroupSpacing = 5
    
    let layout = UICollectionViewCompositionalLayout(section: section)
    
    return layout
}

 

์• ํ”Œ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด estimated๋Š” 

 

Use an estimated value if the size of your content might change at runtime, such as when data is loaded or in response to a change in system font size. You provide an initial estimated size and the system computes the actual value later.

 

์š”์•ฝํ•ด์„œ ๋ฒˆ์—ญํ•˜๋ฉด, estimated๋ฅผ ์‚ฌ์šฉํ•ด๋ผ ๋งŒ์•ฝ ๋„ˆ์˜ content๊ฐ€ ๋Ÿฐํƒ€์ž„ ์ค‘์— ๋ฐ”๋€๋‹ค๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๊ฑฐ๋‚˜ ํฐํŠธ ์‚ฌ์ด์ฆˆ๊ฐ€ ๋ฐ”๋€Œ์–ด์„œ ๋†’์ด๋ฅผ ๋ฐ”๊ฟ”์ค˜์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์ฒ˜๋Ÿผ.

 

๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ ๋†’์ด๋ฅผ Dynamicํ•˜๊ฒŒ ๋ฐ”๊ฟ”์ฃผ๊ธฐ ์œ„ํ•ด group๊ณผ item์˜ ๋†’์ด๋ฅผ estimated๋กœ ์„ค์ •ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

 

Cell ๋งŒ๋“ค๊ธฐ

 

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ์…€์„ ๋งŒ๋“ค์–ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

import UIKit
import SnapKit

final class DynamicCell: UICollectionViewCell {
    
    static let id = String(describing: DynamicCell.self)
    
    private lazy var myLabel: UILabel = {
        let label = UILabel()
        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        
        return label
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.backgroundColor = .secondarySystemBackground
        layout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(with text: String) {
        myLabel.text = text
    }
}

extension DynamicCell {
    func layout() {
        myLabel.snp.makeConstraints {
            $0.top.equalTo(contentView.snp.top).offset(8)
            $0.leading.equalTo(contentView.snp.leading).offset(12)
            $0.bottom.equalTo(contentView.snp.bottom).offset(-8)
            $0.trailing.equalTo(contentView.snp.trailing)
        }
    }
}

 

UILabel์ด ์•ฝ๊ฐ„์˜ Offset์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋‹จ์ˆœํ•œ ์…€์ž…๋‹ˆ๋‹ค.

 

์œ„์˜ ๋ ˆ์ด์•„์›ƒ๊ณผ ์…€์„ ์ด์šฉํ•ด์„œ ๊ฐ„๋‹จํžˆ ๋งŒ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ž˜ ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

 

 

๋ฌธ์ œ๊ฐ€ ์—†์–ด ๋ณด์ด๋„ค์š”, ๊ธ€์˜ ํฌ๊ธฐ์— ๋งž์ถฐ ์ž˜ ๋‚˜์˜ต๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ ์…€๋“ค์„ ์ข€ ๋” ๋งŽ์ด ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

 

 

์˜ค ์ž˜๋‚˜์˜ค๋Š” ๊ฑฐ ๊ฐ™๋„ค์š”,

 

์Šคํฌ๋กค์„ ํ•œ ๋ฒˆ ํ•ด๋ณผ๊นŒ์š”?

 

 

์Œ ์—๋Ÿฌ๊ฐ€ ๋‚˜์˜ค๋„ค์š”.

 

์ •๋ฆฌํ•ด ๋ณด์ž๋ฉด, ์ง€๊ธˆ View์˜ top ์ด๋ž‘ bottom์„ ์…€์—์„œ 8์”ฉ inset์„ ์คฌ๋Š”๋ฐ, ์…€์˜ ๋†’์ด๊ฐ€ 1์ด๋ผ์„œ ์ด๊ฒƒ๋“ค์„ ๋™์‹œ์— ๋งŒ์กฑํ•  ์ˆ˜ ์—†๋‹ค.

 

๋ผ๋Š” ๊ฒƒ ๊ฐ™๋„ค์š”.

 

์ง€๊ธˆ ์•„๋ž˜ ๋ณด์ด์ง€ ์•Š๋˜ ์…€๋“ค์€ ๊ธฐ๋ณธ์ ์œผ๋กœ estimated(1)์—์„œ ์ฃผ์–ด์ง„๋ฐ๋กœ 1๋กœ ์ผ๋‹จ ์„ค์ •์ด ๋˜์–ด์žˆ์–ด์„œ ๋‚˜๋Š” ์˜ค๋ฅ˜ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ estimated๋ฅผ ๋” ํฌ๊ฒŒ ์ฃผ๋ฉด ์ด ์—๋Ÿฌ๊ฐ€ ์‚ฌ๋ผ์ง€๊ธด ํ•ฉ๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์…€์˜ ์ตœ๋Œ€ ํฌ๊ธฐ๋ฅผ ์•Œ์ง€ ๋ชปํ•œ๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š” ์ข‹์€ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์ด ์•„๋‹Œ ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

์ €๋Š” ์—ฌ๊ธฐ์„œ ์ด ์—๋Ÿฌ๋ฅผ ์—†์• ๊ธฐ ์œ„ํ•ด ์ฐธ ๋งŽ์€ ์‚ฝ์งˆ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

ํ•ด๊ฒฐ๋ฒ•๋ถ€ํ„ฐ ๋งํ•˜๋ฉด, ์…€์„ ๋งŒ๋“ค ๋•Œ Constraint์— priority๋ฅผ ์ž˜ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

func layout() {
    myLabel.snp.makeConstraints {
        $0.top.equalTo(contentView.snp.top).offset(8)
        $0.leading.equalTo(contentView.snp.leading).offset(12)
        $0.bottom.equalTo(contentView.snp.bottom).offset(-8)
        $0.trailing.equalTo(contentView.snp.trailing)
    }
}

 

์—ฌ๊ธฐ์„œ

 

์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ”๊ฟ”์ค๋‹ˆ๋‹ค.

 

func layout() {
    myLabel.snp.makeConstraints {
        $0.top.equalTo(contentView.snp.top).offset(8).priority(.high)
        $0.leading.equalTo(contentView.snp.leading).offset(12)
        $0.bottom.equalTo(contentView.snp.bottom).offset(-8)
        $0.trailing.equalTo(contentView.snp.trailing).priority(.high)
    }
}

 

top์ด๋ž‘ bottom์— pritority๋ฅผ ๋†’์—ฌ์คฌ์Šต๋‹ˆ๋‹ค.

 

๋‹ค์‹œ ์‹คํ–‰ํ•ด ๋ณผ๊นŒ์š”.

 

 

์—๋Ÿฌ๋„ ์•ˆ ๋œจ๊ณ  ์…€ ๋†’์ด๋„ ์•Œ๋งž๊ฒŒ ์ž˜ ๋‚˜์˜ค๋„ค์š”.

 

 

 

์ €๋Š” ์ฒ˜์Œ์— ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด cell์˜ height constraint๋ฅผ update ํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ์š”,

 

"์•„ ์…€์˜ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ์ž‘์•„? ๊ทธ๋Ÿฌ๋ฉด ๋‚ด๊ฐ€ ์•Œ๋งž๊ฒŒ ๋ฐ”๊ฟ”์ค„๊ฒŒ"๋ผ๋Š” ์ƒ๊ฐ์ด์—ˆ์ฃ ..

 

๊ทธ๋Ÿฌ๋ฉด 

 

Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionViewCell that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing

 

์ด๋ ‡๊ฒŒ ๋„ค๊ฐ€ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†๊ณ  ์ด์ƒํ•˜๊ฒŒ ๋‚˜์˜ฌ ๊ฑฐ์•ผ, ๋ผ๋ฉด์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

๋„ˆ๊ฐ€ ๋ชปํ•˜๋‹ˆ๊นŒ ๋‚ด๊ฐ€ ํ•ด์ฃผ๊ฒ ๋‹ค๋Š”๋ฐ ์™œ ๋ง‰์•„ ์ด๋ ‡๊ฒŒ๋„ ์ƒ๊ฐํ–ˆ์—ˆ์ฃ . ๊ฒฐ๊ตญ ์ œ๊ฐ€ ์ž˜๋ชปํ•œ ๊ฑฐ์˜€์ง€๋งŒ.

 

๊ทธ๋ž˜์„œ ์ผ๋‹จ์€ tableView๋กœ ๊ต์ฒดํ•˜๊ณ  automaticDimension์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

ํŠน๋ณ„ํ•œ ๋ ˆ์ด์•„์›ƒ์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด ์ด๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ธ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.