iOS/Swift

[Swift] 무한 배너 만들기(Make a Infinite slide banner with carousel)

HUISOO 2022. 9. 20. 13:39

이 포스트는 UICollectionView의 Carousel View로 만든 배너를 무한하게 스크롤하게 만들기 위한 포스팅입니다 :)

(참고: [Swift] 배너 만들기(Make a Slide banner with Carousel))

 

Carousel은 우리말로 회전목마를 뜻합니다.

Carousel View는 일정한 형태를 유지하며 무한히 회전하는 View를 말하지만, 일정하게 Slide 되는 View를 칭하기도 합니다.

 

 최종 수정일 - 22. 09. 20 PM 1:20



개요

 

1. 기존의 셀에서 앞 뒤에 2개의 셀을 추가해준다.

설명을 위한 이미지

 

2. 추가 영역에 도달하면 해당 데이터와 원본 데이터가 동일한 셀로 이동한다.

- 7번 셀에 도달한다면 스크롤을 2번 셀로 이동한다. 

- 1번 셀에 도달한다면 스크롤을 6번 셀로 이동한다. 

 

3. 추가 데이터 없이 무한히 스크롤이 가능하다.

 


방법

 

1. 이전 포스팅에서 만든 UICollectionView 생성

더보기
@IBOutlet weak var collectionView: UICollectionView!

var cellSize: CGSize = .zero
let minimumLineSpacing: CGFloat = 8
let data: [UIColor] = [.systemRed, .systemBlue, .systemGreen, .lightGray, .darkGray]

func setupCollectionView() {
    cellSize = CGSize(width: UIScreen.main.bounds.width - (minimumLineSpacing * 4), height: 128)

    collectionView.delegate = self
    collectionView.dataSource = self
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return data.count
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return cellSize
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return minimumLineSpacing
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath)

    cell.backgroundColor = data[indexPath.row]

    return cell
}


func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let cellWidthIncludingSpacing: CGFloat = cellSize.width + minimumLineSpacing

    let estimatedIndex = scrollView.contentOffset.x / cellWidthIncludingSpacing
    let index: Int
    if velocity.x > 0 {
        index = Int(ceil(estimatedIndex))
    } else if velocity.x < 0 {
        index = Int(floor(estimatedIndex))
    } else {
        index = Int(round(estimatedIndex))
    }

    targetContentOffset.pointee = CGPoint(x: (CGFloat(index) * cellWidthIncludingSpacing) - (minimumLineSpacing * 2), y: 0)
}

기본 Carousel View

 

 

2. 앞 뒤에 셀 추가하기

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    let numberOfItems = 2 + data.count + 2
    return numberOfItems
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath)
    let numberOfItems = 2 + data.count + 2

    // 반복되는 것처럼 보여야 하므로 위 사진과 같이 보이도록 데이터를 넣어준다.
    switch indexPath.row {
    case 0:
        cell.backgroundColor = data[data.count - 2]
    case 1:
        cell.backgroundColor = data[data.count - 1]
    case numberOfItems - 2:
        cell.backgroundColor = data[0]    // 예) 뒤에서 두번째 셀에는 첫번째 데이터가 보이도록 한다.
    case numberOfItems - 1:
        cell.backgroundColor = data[1]
    default:
        cell.backgroundColor = data[indexPath.row - 2]
    }

    return cell
}

반복되고 있지만 무한하지 않다

 

3. 셀의 시작 위치 변경

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // 최초 시작 셀은 실제 데이터의 첫번째가 오는 3번째 셀부터 보이도록 한다.
    collectionView.reloadData()
    collectionView.setContentOffset(CGPoint(x: contentOffsetX(for: 2), y: 0), animated: false)
}

func contentOffsetX(for index: Int) -> CGFloat {
    let cellWidthIncludingSpacing: CGFloat = cellSize.width + minimumLineSpacing
    return (CGFloat(index) * cellWidthIncludingSpacing) - (minimumLineSpacing * 2)
}

 

4. 추가 영역에 도달하면 스크롤 위치 변경하기

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let contentOffset = scrollView.contentOffset
    let numberOfItems = 2 + data.count + 2

    // 동일한 데이터를 가진 셀로 이동하도록 ContentOffset 변경
    if contentOffset.x > contentOffsetX(for: numberOfItems - 2) {
        collectionView.setContentOffset(CGPoint(x: contentOffsetX(for: 2), y: 0), animated: false)
    } else if contentOffset.x < contentOffsetX(for: 1) {
        collectionView.setContentOffset(CGPoint(x: contentOffsetX(for: numberOfItems - 3), y: 0), animated: false)
    }
}

무한하게 스크롤이 가능하다