ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS]Pagination & CollectionView prefetchItemsAt 사용
    Apple🍎/iOS 2025. 1. 16. 16:41

    Pagination

    CollectionView, TableView의 경우, ScrollView를 상속받기 때문에 많은 데이터를 스크롤로 보여주기 좋은 방식이다.
    스크롤 방식에 필요한 데이터를 API 통신으로 한번에 받아오는 경우, 시간과 리소스 문제가 클 수 있다.
    그러므로 스크롤의 위치나 페이지로 넘기는 방식을 통해 끊어서 데이터를 요청하는 방식을 “Pagination”이라고 한다.

    Pagination 종류

    1. Offset based pagination
    2. Cursor based pagination

    1. offset based pagniation

    오프셋 번호를 기반으로 보여주는 것

    1번: 1 ~ 100
    2번: 101 ~ 200
    3번: 201 ~ 300

    Ex. query=스파이더맨&display=100&start=1

    • 서버의 데이터 변화가 잦지 않은 곳에 사용
    • 중복 데이터가 들어가는게 단점(들어오는 데이터가 갑자기 많아지면, 페이지네이션으로 인해 다음 페이지에 중복 데이터가 생김)

    카카오 도서 API

    만약 is_end와 같이 끝 페이지라는 것을 안알려주면, 우리가 직접 계산해야한다.
    pageble_count, total_count 이 두개의 숫자를 비교해서 직접 계산하는 식으로 계산해, end 플래그를 만들어 사용하자.

    2. Cursor based pagination

    bundle id와 같이 중복되지 않는 id를 아이템별로 갖고 있는 데이터에서 사용한다.
    인스타 타임 라인, 채팅 데이터에 사용되는 pagination이며
    사용자가 보고 있는 시점에 데이터가 추가 되어도, 업데이트 하지 않고, 현재 시점에 가져온 데이터만 보여주는 방식이다.
    (인스타 타임라인도 보면, 데이터 추가로 뷰 업데이트를 자주 하는 것 보다, 쭉 보여주는 게 중요하기 때문에)

    Ex. ?cursor=dkfXcvjwoek ⇒ “dkfXcvjwoek" 기준 다음 데이터 가져오기

    쿼리로 cursor123 을 입력하면, cursor123 뒤 데이터부터 가져온다는 뜻이다.

    Pagination 을 구현 방식

    TableView로 무한 스크롤뷰를 만든다고 생각했을 때,

    1. TableView willDisplayCell(사용자에게 이 셀이 곧 뜰거같아) 시점 함수를 활용
      • 셀 생성(중구난방으로 생겨남) → 시점이 어려워져서 요즘엔 잘 안씀
    2. 스크롤 뷰의 offset 활용하는 방법
      • tableView, scrollView는 모두 오프셋을 받음
      • 마지막 데이터 - 현재 보여지는 데이터 ≤ 100 이면, 마지막 스크롤 페이지라는 것을 인식해 데이터를 요청한다 ⇒ 계산의 판단이 어려움
    3. UITableViewDataSource - prefetching protocol 사용TableView, CollectionView 둘다 DataSourcePrefetching 라는 프로토콜을 갖고 있다.
      이 프로토콜의 함수인 아래 함수를 통해 스크롤 시점과 데이터를 편하게 파악할 수 있다.
    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath])

    prefetchItemsAt indexPath: [IndexPath]

    구현 하기전에 중요한 prefetch 함수에 대해 설명하자면,
    prefetchItemsAt 에 담기는 IndexPath는, 미리 스크롤 내리면서 다음에 올 데이터를 담고 있는 배열이다.
    미리 가져오는 아이템이라고 해서 prefetch 이다.
    Ex. 20개의 이미지 데이터를 받아온 화면을 스크롤 할때, prefetchItemsAt에 담기는 번호를 찍어보면,

    extension ViewController: UICollectionViewDataSourcePrefetching {
        func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
            for item in indexPaths {
                print(item.row)
            }
        }
    }

    스크롤이 다 안내려왔음에도 19번째 마지막 데이터가 미리 담겨있는 것을 알 수 있고,
    마지막 스크롤에 도달했을 때는, 그 앞엔 12, 13 즉 위로 올라가는 것을 대비해 담아놓는 것을 알 수 있다.

    구현 방법

    collection view로 20개의 이미지 데이터를 Unsplash api를 통해 받아온다는 것을 가정하에 시작한다.
    간단하게 스크롤을 내렸을 때, 마지막에 맞춰 통신을 해오는 것으로 구현하려 한다.

    1. prefetch를 위한 delegate 설정

    collectionView.prefetchDataSource = self

     
        override func viewDidLoad() {
            super.viewDidLoad()
            collectionView.register(SearchImageCollectionViewCell.self, forCellWithReuseIdentifier: SearchImageCollectionViewCell.id)
            collectionView.delegate = self
            collectionView.dataSource = self
            collectionView.prefetchDataSource = self
            callRequest()
        }
    extension ViewController: UICollectionViewDataSourcePrefetching {
        func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
            <#code#>
        }
    }

     

     2. API 통신에 필요한 변수 설정

    • dataList: API 통신의 데이터 구조인 Result 배열을 담는 변수
    • page: 쿼리에 필요한 페이지 변수
    • isEnd: 마지막 페이지인지 판별하는 변수 

    Alamofire GET 통신을 받은 데이터를 dataList 넣어주고 있다.
    response에 totalPage라는 마지막 페이지 숫자를 알려주는 변수가 있어 isEnd는 이를 이용하려고 한다.
    현재 API 통신은, page 번호 별로 20개의 데이터를 가져올 수 있다.
    (Ex. upsplash photo search api 사용하고 있다)

    class ViewController: BaseViewController, UISearchBarDelegate {
    
        let searchBar = UISearchBar()
        lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: createCollectionLayout())
        var dataList: [Result] = []
        var page: Int = 1
        var isEnd: Bool = false

     

    3. Alamofire 요청에서 데이터 및 변수 설정

    func callRequest() {
        let url = "https://api.unsplash.com/search/photos?query=flower&page=\(page)&per_page=20&order_by=latest&color=yellow&client_id=\(APIKey.search.value)"
        
        AF.request(url, method: .get)
            .responseDecodable(of: SearchData.self) { response in
                switch response.result {
                    
                case .success(let value):
                    **if value.total_pages == self.page { self.isEnd = true } // 마지막 페이지면 isEnd -> true
                    self.dataList.append(contentsOf: value.results) // dataList에 데이터 추가
                    self.collectionView.reloadData() // collectionView 업데이트**
                case .failure(let error):
                    print(error)
                }
            }
     }

     

    4. prefetch 함수 설정

    dataList의 마지막 데이터까지 오면, callRequest로 한번 더 통신해서 다음 데이터 채우기

    extension ViewController: UICollectionViewDataSourcePrefetching {
        func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
            for item in indexPaths {
                if !isEnd && item.row == dataList.count - 2 {
                    page += 1
                    callRequest()
                }
            }
        }
    }

     

    완성본

    계속 스크롤해도, 이미지가 안끊기는걸 볼 수 있다.

     

    참고 자료

    https://www.contentful.com/blog/graphql-pagination-cursor-offset-tutorials/

    https://developer.apple.com/documentation/uikit/uicollectionviewdatasourceprefetching/collectionview(_:prefetchitemsat:)

Designed by Tistory.