blanq 개발일기7 – uicollectionview

개인시간을 쪼개서 작업하는게 요즘 들어서 점점 더 힘들게 느껴진다. 거의 한달동안은 ios캘린더(일정) 연동 이외에 아무것도 하지 않았다. 회사 끝나고 집에 와서 하루에 2시간 정도? 캘린더 연동은 작업량을 줄일수 있는 최대한으로 줄여서 작업하려고 노력했고 최소한의 노력으로 어느 정도의 결과물이 나온거 같다.

blanq의 캘린더 월단위 화면, 저널쪽의 주단위 화면 등 대부분은 무한 스크롤 방식이다. 월단위 화면과 주단위 화면 모두 collectionview를 이용해 만들었고 빠르게 무한 스크롤할 수 있게 노력했다.

가장 어려웠던 점 3가지
1. 빠르게 무한 스크롤 가능하고 원하는 지점으로 이동해야 한다.
2. 회전했을 때 적절한 offset으로 이동하여 화면에 잘 표시함
3. 부드러운 스크롤은 필수

1. 빠르게 무한스크롤은 scrollViewDidScroll 함수에서 collection view offset을 재계산하고 reloadData() 호출하는 방식이다.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    recenter()
}

func recenter() {
    let offset = max(collectionView.contentOffset.y, 0)
    let content = collectionView.contentSize.height
    let minCellSizeHeight = 100.0 //각자 구현에 따라 다름
    let boundsMax = collectionView.bounds.maxY
    if offset < minHeight || boundsMax + minHeight > content {
        //offset 재계산
        //...
        collectionview.reloadData()
    }
}


2. 디바이스 회전시 회전 애니메이션때 scrollViewDidScroll 함수도 호출되는데 여기서 offset 계산하지 않고 애니메이션 끝나면 collectionview layout 무효화 & view reload

3. 부드러운 스크롤
구글에 collection view 스크롤을 검색하면 choppy/bumpy scroll, smooth scroll 등이 많이 등장한다. 사용자 입장에서는 부드러운 스크롤은 당연하고 뚝뚝 끊어지는 스크롤은 아주 나쁜 경험으로 여겨진다. 사실 부드러운 스크롤은 구현하기가 쉽지 않다. 아주 많은량의 데이터를 화면에 표시하려면 부드러운 스크롤은 구현할 수 없을지도 모른다. 애플 문서에 따르면 스크롤을 하면서 16ms내로 화면에 표시하지 못하면 스크롤되는 프레임이 누락되어 끊어지는 스크롤이 된다.
blanq는 Core Data에서 기간별 데이터를 가져와서 화면에 표시를 해야 하는데 하루에 데이터가 5개 정도만 되어도 메인스레드 하나만 사용해서는 부드러운 스크롤을 구현할 수 없다. 그래서 데이터는 백그라운드 스레드에서 가져와서 표시했고 collectionview datasource 에서 제공하는 prefetch 함수에서도 데이터를 가져왔다. 아래와 같은 순서대로 호출된다. 코드로 정리하면…

func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
        //데이터 쿼리
        DataManager.shared.loadBackground(range, preFetch: true) { (_) in
        }
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        //...  
        return UICollectionViewCell()
}

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        let updateCellClosure: () -> Void = { [weak self]  in
            guard let strongSelf = self else {
                return
            }
            let _cell = cell as? WeekCell
            _cell?.reload()
        }
        
        //데이터 쿼리, 이미 가져왔으면 바로 완료됨
        DataManager.shared.loadBackground(range, preFetch: false) { (_) in
            updateCellClosure()
        }
}

애플에서 좀 더 부드러운 스크롤을 위해서 prefetch & prefetch cancel 기능을 iOS8(?)부터인가 지원하기 시작했다. blanq에서는 어짜피 데이터는 가져와야 하니까 prefetch cancel은 구현하지 않았다. collectionview 는 호출되는 순서도 잘 알고 있어야 부드러운 스크롤링 구현이 가능하다. 한가지 특이한 점은 collectionview 의 isPagingEnabled 가 true 일때와 아닐때 또 무한 스크롤링이 가능하도록 스크롤하면서 계속 offset 체크, 재계산, reloadData() 하는것과 맞물려 데이터 로드를 어디서 해야 좀 더 부드럽게 스크롤되지는 달라졌다.

2 thoughts on “blanq 개발일기7 – uicollectionview

    1. 댓글 감사합니다~ 그렇게 대단한 소스가 아닙니다. 오픈 소스라기 보단 그냥 소개 정도로 생각해 주시면 좋겠습니다.
      필요하시면 소스 공개할 수도 있습니다!

답글 남기기

이메일 주소는 공개되지 않습니다.