Swift로 라이브러리 없이 무한 배너 구현하기 (feat. CollectionView)

Danna 다나·2021년 10월 14일
1

우당탕탕 해결기

목록 보기
5/7
post-thumbnail

외주 하는 곳에서 이번에는 실시간 검색어 애니메이션을 구현해달라고 했다.

이렇게 자동으로 롤링 되는 애니메이션이다.

👉🏻 무한 배너가 어려운 이유

얼핏 보면 쉬워보이지만, 따져야 할 것들이 조금 있어 까다로운 기능이다.

  1. 무한으로 셀이 생성돼야 한다
  2. 자동으로 스크롤 돼야 한다

사실 작년 이맘때에 이 기능을 구현해본 적이 있는데,
그때는 라이브러리를 사용했었다.

하지만 내가 지금 하고 있는 프로젝트는 이미 프로젝트 자체가 너무 무겁기도 하고 더이상의 라이브러리는 안 쓰거나 최소화하는 게 나을 거 같아 라이브러리 없이 야매로 이 기능을 구현해보기로 했다.
(사실 말이 장황했지만 그냥 고집인 거 같기도 하다)

👉🏻 야매 로직

일단 CollectionView를 사용하기로 결정했고,
내가 머리로 짠 로직은 이렇다.
1. 보여지는 셀이 2개이므로 그거보다 하나 더 많은 3개의 셀을 만든다.
2. 마지막 셀로 넘어갔을 때 재빠르게 첫번째 셀로 되돌린다 (여기서 animation: false 설정해서 되돌아가는 게 안 보이게)

(1) 셀을 자동으로 스크롤 해주는 코드

우선, 셀 세 개를 넘기는 코드는 이렇게 작성했다.

func weatherTimer() {
    let _: Timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { (Timer) in
        self.weatherMove()
    }
}

func weatherMove() {
    // 다음 페이지로 전환
    currPage += 1
    fundWeatherCollectionView.scrollToItem(at: NSIndexPath(item: nowPage, section: 0) as IndexPath, at: .bottom, animated: true)
}

변수로 var currPage = 0을 선언해주고,
viewDidLoad에서 weatherTime()을 호출해주면, 설정된 Timer에 따라 weatherMove() 메서드를 2초마다 불러주게 된다.

Timer.scheduledTimer()을 더 자세히 살펴보면,
withTimeInterval은 타이머를 실행할 시간(초)를, repeats는 true일 때 해당 타이머를 계속해서 반복하고, false일 때는 1번 실행하고 끝난다.

weatherMove() 메서드에서는 currPage를 하나씩 증가시킨 후,
currPage로 이동시키는 scrollToItem()을 사용한다.
*** 여기서 animated: true로 설정하면 스크롤 되는 애니메이션이 보이고, false로 설정하면 보이지 않는다.

(2) 마지막 셀에서 첫 셀로 돌려주는 코드

여기서 포인트는 아무도 모르게 돌려야 한다는 것이다.
위에서 잠깐 힌트가 나왔는데, scrollToItem에서 animated를 false로 설정해주면, 아무도 모르게 되돌릴 수 있을 것이다.

그래서 되돌리는 코드를

func scrollTofirstIndex() {
    fundWeatherCollectionView.scrollToItem(at: NSIndexPath(item: 0, section: 0) as IndexPath, at: .top, animated: false)
    currPage = 0
}

이렇게 작성했다.

(3) 타이밍 맞추기

마지막 셀로 갔다가 첫 셀로 돌아오는데, 이게 만약 똑같이 2초의 텀을 가지고 있다면, 아무리 우리가 아무도 모르게 첫 셀로 돌려놨다고 해도 사용자는 똑같은 셀을 4초 동안 봐야 하는 문제가 있다. 그래서 타이머 영향을 받지 않으면서 눈속임하는 마지막 코드가 필요했다.

사실 가장 많이 헤맨 부분이기도 한데, 이스케이핑 클로저도 써보고 코드의 순서도 바꿔보았지만, 결국 내가 선택한 방법은 DispatchQueue.main.asyncAfter로 delay를 주는 방법이다.

그 이유는 2->3번째 셀 넘어갈 때의 애니메이션이 보여야 하고, 그 후 바로 아무도 모르게 1번째 셀로 넘어와야 하기 때문에 delay를 줘서 애니메이션만 보여지게 한 후 바로 1번째 셀로 돌리는 방법을 사용했다.

if self.currPage == self.topWeatherData.count-1 {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
        self.scrollTofirstIndex()
    }
}

이런 식으로 현재 보여지고 있는 페이지가 마지막 페이지일 때, 0.3초의 딜레이(페이지가 넘어가는 애니메이션이 보여지는 시간)을 주고 바로 첫번째 인덱스로 돌아가는 코드다.

(4) 전체 코드

이런 식으로 구현을 하니까 정말 나조차도 모르게 부드럽게 구현이 되었다.
전체 코드는 다음과 같다.

var currPage: Int = 0

override func viewDidLoad() {
    weatherTimer()
}

func weatherTimer() {
    let _: Timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { (Timer) in
        self.weatherMove()
    }
}

func weatherMove() {
    currPage += 1
    fundWeatherCollectionView.scrollToItem(at: NSIndexPath(item: currPage, section: 0) as IndexPath, at: .bottom, animated: true)
    
    if self.currPage == self.topWeatherData.count-1 {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            self.scrollTofirstIndex()
        }
    }
}

func scrollTofirstIndex() {
    fundWeatherCollectionView.scrollToItem(at: NSIndexPath(item: 0, section: 0) as IndexPath, at: .top, animated: false)
    currPage = 0
}

끊어서 설명해서 코드가 길어보였지만, 코드는 정말 짧다.

profile
요즘은 https://welcometodannas.tistory.com/에 더 많은 글을 씁니다.

0개의 댓글