Potato
์•ˆ๋…•ํ•˜์„ธ์š”, ๊ฐ์žก๋‹ˆ๋‹ค?๐Ÿฅ” ^___^ ๐Ÿ˜บ github ๋ฐ”๋กœ๊ฐ€๊ธฐ ๐Ÿ‘‰๐Ÿป

potato's iOS Story/๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๋งŒ๋‚œ ์นœ๊ตฌ๋“ค

[UIKit] collectionView์˜ ์…€๋“ค์„ ์™ผ์ชฝ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ •๋ ฌํ• ๊นŒ!? (UICollectionViewFlowLayout, UICollectionViewDelegateFlowLayout)

๊ฐ์ž ๐Ÿฅ” 2022. 11. 4. 03:24
๋ฐ˜์‘ํ˜•

 

์ง€๊ธˆ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š” ๋ทฐ

Designed by. ์•ˆ์€์ง€ (์ธ์Šคํƒ€: @ahnunji)

์ด๋ ‡๊ฒŒ ์ปฌ๋ ‰์…˜๋ทฐ๋ฅผ ๋„์›Œ์•ผํ•œ๋‹ค. ๋‚ด๊ฐ€ ์ด์ „์— ์งœ๋†จ๋˜ ์ฝ”๋“œ๊ฐ€ ์žˆ์–ด์„œ ๊ทธ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ• ๊ฒƒ์ด๋‹ค.

 

์ปฌ๋ ‰์…˜๋ทฐ๋ฅผ ๋„์› ๋‹ค. ๊ทผ๋ฐ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค.

์ด๋ ‡๊ฒŒ ๋นˆ๊ณต๊ฐ„์ด ์ค‘์•™์— ์ƒ๊ธฐ๋Š” ๊ฒƒ์ด๋‹ค! ๊ธฐ๋ณธ์ ์œผ๋กœ ์ปฌ๋ ‰์…˜๋ทฐ๋Š” ์–‘์ชฝ ๋ ˆ์ด์•„์›ƒ์„ ๋งž์ถ”๋„๋ก ์ •๋ ฌ์ด ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ์™ผ์ชฝ์œผ๋กœ ์ •๋ ฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋”ฐ๋กœ ์ปค์Šคํ…€ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

๋„์ „ํ•ด๋ณด์ž!

 

layoutAttributesForElements()

์ธํ„ฐ๋„ท์— ๋งŽ์€ ๋ ˆํผ๋Ÿฐ์Šค๊ฐ€ ์žˆ์—ˆ๊ณ , ๊ทธ ์ค‘ ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๊ฐ™์•˜๋‹ค.

https://velog.io/@yoonah-dev/CollectionView-Left%EC%A0%95%EB%A0%AC

 

CollectionView Left์ •๋ ฌ

CollectionView Cell๋“ค์„ ์™ผ์ชฝ ์ •๋ ฌํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

velog.io

 

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) ->  [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
        var leftMargin: CGFloat = 0.0
        var maxY: CGFloat = -1.0
    
        attributes?.forEach { layoutAttribute in
            guard layoutAttribute.representedElementCategory == .cell else {
                return
            }
            if layoutAttribute.frame.origin.y >= maxY {
                leftMargin = 0.0
            }
            layoutAttribute.frame.origin.x = leftMargin
            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }
        return attributes
    }
}

์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ ๊ฐ™์•˜๋‹ค. ์ผ๋‹จ ์›๋ฆฌ๋ฅผ ์•Œ์•„์•ผ ๋‚ด ์ฝ”๋“œ์— ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ ํ•˜๋‚˜ํ•˜๋‚˜ ๋œฏ์–ด๋ณด์ž.

ํ•ด๋‹น ์ฝ”๋“œ์— ๋Œ€ํ•ด์„œ ๊ฐ„๋žตํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด,

์ปค์Šคํ…€ํ•ด์„œ ๋งŒ๋“  ํ•ด๋‹น ํด๋ž˜์Šค์—์„œ๋Š”, UICollectionViewLayoutAttributes๋ฅผ ์ด์šฉํ•ด์„œ ์ปฌ๋ ‰์…˜๋ทฐ์˜ ๋ชจ์–‘์„ ์กฐ์ ˆํ•˜๋ ค๊ณ ํ•ฉ๋‹ˆ๋‹ค.

  • attributes: ์—ฌ๊ธฐ์—๋Š” ํ˜„์žฌ ๊ฐ๊ฐ์˜ ์…€ ๋‚ด๋ถ€์— ์žˆ๋Š” ์†์„ฑ๋“ค์„ ์ „๋ถ€ map ํ•ด์„œ array ํ˜•ํƒœ๋กœ ๋„ฃ์–ด๋‘” ๋ฐฐ์—ด์ž…๋‹ˆ๋‹ค.
    • layoutAttributesForElements : ํ•ด๋‹น ์ธ์Šคํ„ด์Šค๊ฐ€, ๊ฐ๊ฐ์˜ cell ๋‚ด๋ถ€์— ์žˆ๋Š” ๋ชจ๋“  ๋ ˆ์ด์•„์›ƒ์— ๋Œ€ํ•œ ์†์„ฑ์„ ๊ฒ€์ƒ‰ํ•ด์ฃผ๊ณ , attributes์— ๋‹ด์Šต๋‹ˆ๋‹ค.
  • leftMargin: ์™ผ์ชฝ ์ •๋ ฌ์„ ํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ€์žฅ ์™ผ์ชฝ์— ์–ผ๋งŒํผ ๋‚จ๊ธฐ๊ณ  ์™ผ์ชฝ์œผ๋กœ ๋ถ™์ผ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ์ˆ˜๋ฅผ CGFloat ํ˜•ํƒœ๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • representedElementCategory: ์–ด๋–ค ๋ฒ”์ฃผ์— ์†ํ•˜๋Š”์ง€ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋‚˜ํƒ€๋‚ด์ค๋‹ˆ๋‹ค. ์ฆ‰, ์ด ์†์„ฑ์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ ์†์„ฑ์ด ์…€, ๋ณด์กฐ ๋ณด๊ธฐ ๋˜๋Š” ์žฅ์‹ ๋ณด๊ธฐ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ธ์ง€ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„ ์†์„ฑ๋“ค์„ ๋น„๊ตํ•ด์„œ, ํ˜„์žฌ cell์˜ ์†์„ฑ์˜ frame์ด maxY ๋ณด๋‹ค ํฌ๋‹ค๋ฉด, ์™ผ์ชฝ์ •๋ ฌ์ด ๋˜์ง€ ์•Š์•˜๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ์•„๊นŒ ์„ค์ •ํ•œ leftMargin๊ฐ’๋งŒ ๋‚จ๊ฒจ๋‘๊ณ  ์™ผ์ชฝ์œผ๋กœ ๋ถ™์—ฌ๋ฒ„๋ฆฌ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ cell์—๋Œ€ํ•œ ์†์„ฑ์„ ์„ค์ •ํ•ด๋‘” layout์„, CollectionView์— ๊ทธ๋Œ€๋กœ ์ ์šฉํ•ด์ฃผ๋ฉด, ์™ผ์ชฝ์ •๋ ฌ์ด ๋˜๋Š” ์†์„ฑ์„ ๊ฐ€์ง„ cell์„ layout์œผ๋กœ ๊ฐ–๊ฒŒ๋˜์–ด, ์™ผ์ชฝ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ ๋„ฃ๊ณ , flowlayout ๋„ฃ๋Š” ๋ถ€๋ถ„์— ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ๋„ฃ์–ด์ฃผ๋‹ˆ๊นŒ ๋ฐ”๋กœ ์™„๋ฃŒ๋˜์—ˆ๋‹ค.

private lazy var collectionView: UICollectionView = {
        let layout = **LeftAlignCollectionViewFlowLayout**()
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        layout.scrollDirection = .vertical
        layout.minimumLineSpacing = Size.cellInterval
        layout.minimumInteritemSpacing = Size.cellInterval
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(MyNewsDrawerCollectionViewCell.self, forCellWithReuseIdentifier: MyNewsDrawerCollectionViewCell.className)
        return collectionView
    }()

 

 

์ฝ”๋“œ์—์„œ leftMargin์„ ์ œ์™ธํ™”๊ณ  ์˜†์œผ๋กœ ์ญ‰ ๋ถ™์ด๊ฒ ๋‹ค๋Š” ๋œป์ด๋‹ค. ๋‚˜๋Š” ์™ผ์ชฝ์— ์™„์ „ํžˆ 0์œผ๋กœ ๋ถ™์—ฌ์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— leftMargin์„ 0์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค.

 

delegateFlowLayout ์ด๋ž‘ ๊ทธ๋ƒฅ FlowLayout์ด๋ž‘ ์ฐจ์ด์ ?

 

๐ŸŸฃ UICollectionViewFlowLayout

  • ์•„๋ž˜ ์ฒ˜๋Ÿผ, ์ฃผ๋กœ ์ดˆ๊ธฐ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ์— ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ์ž์ฃผ ์‚ฌ์šฉํ•œ๋‹ค. delegate ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๊ธฐ ๋–„๋ฌธ์ด๋‹ค.
var homeCollectionView: UICollectionView = {
    let layout = **UICollectionViewFlowLayout**()
    **layout.scrollDirection = .vertical
    layout.minimumLineSpacing = 36
    layout.sectionInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
    layout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width - 20*2, height: 200)**

    let collectionView = UICollectionView(frame: .zero,
                                          collectionViewLayout: layout)
    return collectionView
}()

์œ„์—์ฒ˜๋Ÿผ ์ดˆ๊ธฐ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ์ปค์Šคํ…€ํ•ด์ค€ ๋’ค, ๊ทธ ๋ ˆ์ด์•„์›ƒ์„ ์ƒ์†๋ฐ›๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

class **StretchHeaderLayout**: UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        ... ์ƒ๋žต
        return layoutAttributes
    }
    
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}

class ViewController: UIViewController {
  @IBOutlet weak var stickyCollectionView: UICollectionView!
	let stickyFlowLayout = **StretchHeaderLayout**()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

 

๋™์ž‘ ๋ฐฉ์‹

์•„๋ž˜ ๊ทธ๋ฆผ์€ CollectionView์™€ UICollectionViewFlowLayout ๊ฐ์ฒด๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๊ทธ๋ฆผ์„ ์ฒ˜์Œ ๋ณด๋ฉด, CollectionView๊ฐ€ prepareLayout์„ ํ†ตํ•ด์„œ ๋ ˆ์ด์•„์›ƒ์˜ ์ดˆ๊ธฐ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ , ์ดํ›„ CollectionView์˜ ํฌ๊ธฐ๋ฅผ ๋ฐ›๊ณ , Cell์˜ ํฌ๊ธฐ๋“ค์„ ์ง€์ •ํ•˜๊ณ  ๋ฐ›๋Š” ์ˆœ์„œ๋Œ€๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค! ์˜ฌ๋ ˆ ๐Ÿ™Œ๐Ÿป

๋™์ž‘ํ•˜๋Š” ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์•Œ์•„๋ณด์Ÿˆ๊ตฟ

  • prepare(): ์ดˆ๊ธฐ์— collectionView์˜ ๋ ˆ์ด์•„์›ƒ์„ ์„ค์ •ํ•  ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์‚ฌ์ด์ฆˆ๋ฅผ ์œ„ํ•œ ์–ด๋–ค ์…‹ํŒ…์ด ํ•„์š”ํ•  ๋•Œ, ์ด ๋ฉ”์†Œ๋“œ๋ฅผ override ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (CollectionView์˜ ์ดˆ๊ธฐ์„ค์ •์ด ์•„๋‹Œ ๋ ˆ์ด์•„์›ƒ ์—ฐ์‚ฐ์ด ์ผ์–ด๋‚˜๊ธฐ ์ „์ด๋‹ค.)
  • layoutAttributesForElements(rectr: ) -> [UICollectionViewLayoutAttributes]? : CollectionView์•ˆ์— ์žˆ๋Š” ๋ชจ๋“  ์š”์†Œ์— ๋Œ€ํ•œ Layout์š”์†Œ๋“ค์„ ๋ฆฌํ„ดํ•œ๋‹ค. (ํ˜„์žฌ ํ™”๋ฉด์— ๋ณด์ด๋Š” ์š”์†Œ)
  • layoutAttributesForItem(indexPath: ) -> UICollectionViewLayoutAttributes?: CollectionView์— ์š”๊ตฌํ•œ IndexPath์˜ Layout์š”์†Œ๋“ค์„ ๋ฆฌํ„ดํ•œ๋‹ค.
  • shouldInvalidateLayout(newBounds: ) -> Bool : Bounds์— ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๋•Œ๋งˆ๋‹ค, ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ• ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค.

 

๐ŸŸฃ UICollectionViewDelegateFlowLayout

  • ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํ•ด๋‹น ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•ด์„œ ๋ฐ์ดํ„ฐ์— ๋งž๊ฒŒ collectionView์˜ ๋ ˆ์ด์•„์›ƒ์„ ์ปค์Šคํ…€ํ•œ๋‹ค.
  • ๋ฐ์ดํ„ฐ์— ๋งž๊ฒŒ ์ปค์Šคํ…€ํ—ค์• ํ–ํ•  ๊ฒฝ์šฐ์— ์ข‹๋‹ค.
  • delegate ๋Œ€์‹  ์จ์ฃผ๋ฉด ๋œ๋‹ค..?

์ฐธ๊ณ ์ž๋ฃŒ: https://dongminyoon.tistory.com/29

๋ฐ˜์‘ํ˜•