๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS

[iOS/Swift] Carousel Effect - UIEdgeInset์ด ์žˆ๋Š” UICollectionView ํŽ˜์ด์ง• ํ•˜๊ธฐ

by ํ‹ด๋”” 2022. 5. 31.
728x90
๋ฐ˜์‘ํ˜•

https://www.pexels.com/ko-kr/photo/1446624/

์ •๋ง ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ธ ์ปฌ๋ ‰์…˜ ๋ทฐ ํŽ˜์ด์ง•! UICollectionView ์†์„ฑ์— isPagingEnable์ด ์žˆ์ง€๋งŒ 

์ด๋ ‡๊ฒŒ ์…€๊ณผ ์…€ ์‚ฌ์ด์— minimumLineSpacing์ด ๋“ค์–ด๊ฐ€๊ฑฐ๋‚˜ sectionInset์ด ๋“ค์–ด๊ฐ€๋ฉด ํŽ˜์ด์ง•์ด ์ œ๋Œ€๋กœ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค ๐Ÿฅฒ

scrollViewDidScroll์—์„œ ๋กœ๊ทธ๋ฅผ ์ฐ์–ด๋ณด๋ฉด ๊ทธ ์ด์œ ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋Š”๋ฐ์š”

UIScreen์˜ ๋„“์ด๊ฐ€ 390์ด๊ณ  ํ•œ๋ฒˆ ํŽ˜์ด์ง•์ด ๋ ๋•Œ ํ™”๋ฉด ๋„“์ด ๋งŒํผ ํŽ˜์ด์ง•์ด ๋˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์–ด์š” 

์…€ ์‚ฌ์ด์ฆˆ๋Š” 320์œผ๋กœ ํ™”๋ฉด ๋„“์ด๋ณด๋‹ค ์ž‘๊ณ  minimumLineSpacing์„ 7๋กœ ์„ค์ •ํ•ด ์คฌ๊ธฐ ๋•Œ๋ฌธ์— ํ•œ๋ฒˆ์— ํ™”๋ฉด ๋„“์ด ๋งŒํผ ํŽ˜์ด์ง• ํ•˜๋ฉด

ํ‹€์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค! 

์ขŒ์šฐ ์—ฌ๋ฐฑ ํฌํ•จ, ์…€ ์‚ฌ์ด spacing ๊นŒ์ง€ ํฌํ•จํ•ด์„œ ํŽ˜์ด์ง•์ด ๋˜๊ฒŒ ํ•˜๋Š” UI ๊ฐ€ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š”๋ฐ ์ด ์ฐธ์— ์ •๋ฆฌํ•ด ๋ด…์‹œ๋‹น

scrollViewWillEndDragging

์• ํ”Œ ๊ฐœ๋ฐœ ๋ฌธ์„œ

optional func scrollViewWillEndDragging(_ scrollView: UIScrollView, 
                           withVelocity velocity: CGPoint, 
                    targetContentOffset: UnsafeMutablePointer<CGPoint>)

์šฐ์„  ์‚ฌ์šฉํ•ด ์ค„ ํ•จ์ˆ˜๋Š” scrollViewWillEndDragging!

 

UIScrollViewDelegate์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. UICollectionView๊ฐ€ UIScrollView๋ฅผ ์ƒ์† ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•ด ์ค„ ์ˆ˜ ์žˆ์–ด์š”!

 

์‚ฌ์šฉ์ž๊ฐ€ UIScrollView์˜ ์ปจํ…์ธ ๋ฅผ ์Šคํฌ๋กค๋งํ•˜๋‹ค๊ฐ€ ๋ฉˆ์ถ˜๊ฒฝ์šฐ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ์—๊ฒŒ ์ด๋ฅผ ์•Œ๋ฆฝ๋‹ˆ๋‹ค

์ด๋•Œ velocity๋Š” ํ„ฐ์น˜๊ฐ€ ๋๋‚œ ์‹œ์ ์˜ ์Šคํฌ๋กค ๋ทฐ์˜ ์†๋„์ด๊ณ  targetContentOffset์€ ์Šคํฌ๋กค๋ง ์•ก์…˜์ด ์†๋„๋ฅผ ์ค„์ด๋‹ค๊ฐ€ ๋ฉˆ์ถ”๋Š” ์ง€์ ์˜ ์˜ˆ์ƒ ์˜คํ”„์…‹์ž…๋‹ˆ๋‹ค.

 

์œ ์ €์˜ ์Šคํฌ๋กค ์•ก์…˜์ด ๋๋‚  ๋•Œ ์˜ˆ์ƒ ์ปจํ…์ธ  ์˜คํ”„์…‹์„ ๋ณ€๊ฒฝํ•ด ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

์ „์ฒด ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์š” ๐Ÿ‘‰๐Ÿป ๋งํฌ

class CarouselEffectViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    let cellWidth = UIScreen.main.bounds.width - 70
    let cellHeight = UIScreen.main.bounds.height - 100
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: cellWidth, height: cellHeight)
        layout.scrollDirection = .horizontal
        layout.minimumLineSpacing = 7

        collectionView.contentInset = UIEdgeInsets(top: 0, left: 34, bottom: 0, right: 34)
        collectionView.collectionViewLayout = layout
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(UINib(nibName: "CaraouselCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CaraouselCollectionViewCell")
    }

}

}

extension CarouselEffectViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CaraouselCollectionViewCell", for: indexPath) as? CaraouselCollectionViewCell
        cell?.idx = "\(indexPath.item)"
        return cell ?? UICollectionViewCell()
    }
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        
    }
}

๊ธฐ๋ณธ UI์™€ collectionView, UICollectionViewFlowLayout์— ๋Œ€ํ•œ ์„ค์ •์„ ํ•ด์ค€ ๋’ค ์œ„์—์„œ ์•Œ์•„ ๋ณธ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
            return
        }

        let cellWidth = layout.itemSize.width
        let cellSpacing = layout.minimumLineSpacing
        let contentLeftInset = scrollView.contentInset.left

        let cellWithSpacing = cellWidth + cellSpacing

        let offsetX = targetContentOffset.pointee.x
        let index = (offsetX + contentLeftInset) / cellWithSpacing
        let roundedIndex: CGFloat = round(index)

        let adjustedOffsetX = roundedIndex * cellWithSpacing - scrollView.contentInset.left

        targetContentOffset.pointee = CGPoint(x: adjustedOffsetX, y: scrollView.contentInset.top)
    }

์•Œ์•„๋ณด๊ธฐ ์‰ฝ๊ฒŒ ํ•˜๋ ค๊ณ  ์ž˜๊ฒŒ ์ชผ๊ฐœ์„œ ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ์—ˆ๋Š”๋ฐ ์ง์ ‘ ๋Œ€์ž…ํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹น๐Ÿฆ‘

์—ฌ๊ธฐ ๊นŒ์ง€ ํ•˜๋ฉด ์›ํ•˜๋˜๋Œ€๋กœ spacing๊ณผ inset์„ ํฌํ•จํ•˜๊ณ  ์…€์ด ๊ฐ€์šด๋ฐ๋กœ ์˜ค๋ฉด์„œ ํŽ˜์ด์ง•์ด ๋ฉ๋‹ˆ๋‹ค!!

์ง€๊ธˆ ์ƒํƒœ์—์„œ ์Šคํฌ๋กค ํ•˜๋ฉด ๊ต‰์žฅํžˆ ์Šค๋ฅด๋ฅต ๋„˜์–ด๊ฐ€๋Š” ๋Š๋‚Œ์ด ๋“ค ์ˆ˜ ์žˆ๋Š”๋ฐ ํƒํƒ ๋„˜์–ด๊ฐ€๋Š” ํŽ˜์ด์ง•์„ ์›ํ•˜๋ฉด 

collectionView.decelerationRate = .fast

์„ ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค ํ›„ ์†๊ฐ€๋ฝ์„ ๋—„๋•Œ ๊ฐ์†ํ•˜๋Š” ์†๋„์ธ๋ฐ ์ด ๊ฐ์† ์†๋„๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด ํ„ฑํ„ฑ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค 


์ด์ œ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•ด ๋ด…์‹œ๋‹น ๐Ÿ˜†

        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
            return
        }

์šฐ์„  collectionView์—์„œ ์ง€์ •ํ•ด ์ค€ UICollectionViewFlowLayout์— ์ ‘๊ทผํ•ด์„œ ์ด layout ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๊ฐ€ ์žˆ์–ด์š”!

        let cellWidth = layout.itemSize.width
        let cellSpacing = layout.minimumLineSpacing
        let contentLeftInset = scrollView.contentInset.left

์ด๋ ‡๊ฒŒ ์†์„ฑ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ๊ตณ์ด ์…€์˜ ํฌ๊ธฐ๋‚˜ spacing์„ ๋”ฐ๋กœ ์ „์—ญ์œผ๋กœ ์ „์–ธํ•ด ์ฃผ์ง€ ์•Š์•„๋„ ๋˜์š”!

์—ฌ๊ธฐ์„œ ์•Œ๊ณ  ๋„˜์–ด ๊ฐ€๋ฉด ์ข‹์€๊ฒŒ contentLeftInset์œผ๋กœ ์„ ์–ธํ•œ scrollView์˜ contentInset์ž…๋‹ˆ๋‹ค 

์ด ๊ฐ’์ด ์˜๋ฏธํ•˜๋Š” ๊ฑด ๋ง ๊ทธ๋Œ€๋กœ ์Šคํฌ๋กค ๋ทฐ์˜ ์ธ์…‹์ธ๋ฐ์š” ์ด๊ฒŒ ์™œ ํ•„์š”ํ•œ์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค!

 

collectionView.contentInset = UIEdgeInsets(top: 0, left: 34, bottom: 0, right: 34)

์šฐ์„  ์œ„์—์„œ ์„ค์ •ํ•ด ์ค€ ๊ฐ’์€ ์ด๋ ‡์Šต๋‹ˆ๋‹ค! collectionView์˜ contentInset ์ž์ฒด๋ฅผ 34๋กœ ์„ค์ •ํ•ด ์ฃผ๋ฉด ์Šคํฌ๋กค ๋ทฐ๊ฐ€ 34 ๋งŒํผ ๋ฐ€๋ฆฝ๋‹ˆ๋‹ค!

scrollViewWillEndDragging์—์„œ contentLeftInset ์„ ์–ธ ์•„๋ž˜ ๋ถ€๋ถ„์„ ๋ชจ๋‘ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  scrollViewDidScroll์—์„œ ๋กœ๊ทธ๋ฅผ ์ฐ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹น

 

์Šคํฌ๋กค์ด ๋๋‚œ ํ›„ ๋งˆ์ง€๋ง‰์— -34์—์„œ ๋ฉˆ์ถ˜ ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์–ด์š”! scrollView.contentInset.left ๋˜ํ•œ ๋™์ผํ•˜๊ฒŒ ์ง€์ •ํ•ด ์ค€ ๋Œ€๋กœ 34 ๊ฐ’์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค

collectionView.contentInset = UIEdgeInsets(top: 0, left: 34, bottom: 0, right: 34)

์ด ์ฝ”๋“œ๋ฅผ ์ฃผ์„ ์ฒ˜๋ฆฌ ํ•˜๊ณ  

layout.sectionInset = UIEdgeInsets(top: 0, left: 34, bottom: 0, right: 34)

์ด๋ฒˆ์—๋Š” UICollectionViewFlowLayout์˜ sectionInset ์†์„ฑ์„ ์„ค์ •ํ•ด ์ค๋‹ˆ๋‹ค! ์ด๋ ‡๊ฒŒ ํ•ด๋„ ์ปฌ๋ ‰์…˜ ๋ทฐ์—๋Š” ๋˜‘๊ฐ™์ด ์ธ์…‹์ด ๋“ค์–ด๊ฐ€์ง€๋งŒ ๋‹ค๋ฅธ ์ ์ด ์žˆ์–ด์š”!

layout์˜ sectionInset์„ ์„ค์ •ํ•ด ์ฃผ๋Š” ๊ฒฝ์šฐ ์Šคํฌ๋กค ๋ทฐ์˜ ์ปจํ…ํŠธ ์˜คํ”„์…‹์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•„์š”

 

 

๋‹ค์‹œ ์ฝ”๋“œ๋กœ ๋Œ์•„์™€์„œ

        let cellWithSpacing = cellWidth + cellSpacing

        let offsetX = targetContentOffset.pointee.x
        let index = (offsetX + contentLeftInset) / cellWithSpacing
        let roundedIndex: CGFloat = round(index)

์ด ๋ถ€๋ถ„์„ ๋ด…์‹œ๋‹น!

collectionView.contentInset = UIEdgeInsets(top: 0, left: 34, bottom: 0, right: 34)

๋กœ ์„ค์ •ํ•ด ์ค€ ๊ฒฝ์šฐ์— scrollView.contetnSize.width๋Š” inset์„ ์ œ์™ธํ•˜๊ณ  (์…€ ๋„“์ด * ์…€ ๊ฐฏ์ˆ˜) + (๋งˆ์ง„ ๊ฐฏ์ˆ˜ * (์…€ ๊ฐฏ์ˆ˜ - 1)) ์ž…๋‹ˆ๋‹ค 

์Šคํฌ๋กค ๋ทฐ๊ฐ€ ์ฒ˜์Œ์— ๊ฐ€์žฅ ์™ผ์ชฝ ๋์— ์ดˆ์ ์ด ์žˆ๊ณ  ์˜ค๋ฅธ์ชฝ ๋ฐฉํ–ฅ์œผ๋กœ ์Šคํฌ๋กค ํ•˜๋ฉด ๋ฐ”์šด์‹ฑ์ด ๋๋‚˜๊ณ  ์ตœ์ข…์ ์œผ๋กœ ๊ฐ€๊ณ ์ž ํ–ˆ๋˜ offset์€ ์„ค์ •ํ•ด ๋‘” ์ธ์…‹ -34์ผ๊ฑฐ์—์š”! ์ด๋•Œ contentLeftInset์˜ ๊ฐ’ 34๋ฅผ ๋”ํ•ด์ฃผ๋ฉด ์ฒ˜์Œ ์…€์˜ ์˜์—ญ์€ 0, ๊ทธ ๋‹ค์Œ ์…€์˜ ์˜์—ญ์€ 0 + (์…€ ๋„“์ด + spacing) ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ฒ ์ฃ !

let adjustedOffsetX = roundedIndex * cellWithSpacing - contentLeftInset

์ดํ›„ ์ธ๋ฑ์Šค์— (์…€ ๋„“์ด + spacing) ์„ ๊ณฑํ•ด์„œ ์‹ค์ œ offset์„ ๊ตฌํ•œ ๋’ค ์›๋ž˜ ๋Œ€๋กœ contentIset์„ left ๊ฐ’ ๋งŒํผ ๋ฐ€์–ด ์ค๋‹ˆ๋‹ค. (contentSize๋Š” inset์„ ์ œ์™ธํ•œ ๊ฐ’์ด์ง€๋งŒ offset์€ inset์ด ์ ์šฉ๋œ ๊ฐ’์ด๋ฏ€๋กœ) ๋งŒ์•ฝ inset ์„ ๋นผ์ฃผ์ง€ ์•Š๋Š”๋‹ค๋ฉด?

์Šคํฌ๋กค ํ›„ scrollViewWillEndDragging ์ด ํƒ€๋ฉด์„œ ์ฒซ ๋ฒˆ์งธ ์…€ ๋ถ€ํ„ฐ inset -34 ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์•„ contentOffset์ด 0์ด ๋˜๋ฉด์„œ ์ธ์…‹์ด ์ •์ƒ์ ์œผ๋กœ ์žกํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค

๊ทธ ๋‹ค์Œ ์…€๋„ ๋งˆ์ฐฌ๊ฐ€์ง€

 

์ด ๋ฐฉ๋ฒ•์—๋Š” ๋ฌธ์ œ์ ์ด ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ์šฉ ๐Ÿฆ‘

๊น”์ง ๊น”์ง ์กฐ๊ธˆ์”ฉ ์Šคํฌ๋กค๋ง ํ–ˆ์„ ๋•Œ ์ด๋ ‡๊ฒŒ ์ฟฐ์ฒ™์ฟฐ์ฒ™ ๋˜๋Š” ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ซ ์›๋ž˜ ๊ฐ€๋ ค๋˜ targetOffset๊ณผ ๊ณ„์‚ฐ๋œ offset ์‚ฌ์ด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ๊ฐ™์€๋ฐ ์ด๊ฒƒ๋„ ์กฐ๋งŒ๊ฐ„ ์ˆ˜์ •ํ•ด์„œ ๊ธ€์„ ์จ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค 

 

โญ๏ธ ์ด์ƒํ•œ ์ ์ด๋‚˜ ๋ฌธ์ œ์ ์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€๋กœ ๊ณต์œ  ๋ถ€ํƒ๋“œ๋ ค์š”!!!

 


์ฐธ๊ณ  ์‚ฌ์ดํŠธ

 

[Swift] - Simple Carousel Effect CollectionView With Animation

์ตœ๊ทผ์— ์• ๋‹ˆ๋ฉ”์ด์…˜์— ๊ด€์‹ฌ์ด ๋งŽ์•„์ ธ์„œ ๊ณ„์† ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ด€๋ จ๊ธ€๋งŒ ์˜ฌ๋ฆฌ๊ณ ์žˆ๋„ค์š” ใ…Žใ…Ž ์˜ค๋Š˜ ๊ตฌํ˜„ํ•ด๋ณผ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด์—์š” ๋Œ€ํ‘œ์ ์œผ๋กœ ์Œ์•…์•ฑ์„๋ณด๋ฉด ์•จ๋ฒ”์„ ๋„์šธ๋•Œ ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ๋„์šฐ๋Š”๊ฑธ ๋ณผ์ˆ˜์žˆ

nsios.tistory.com

 

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€