[iOS] UITableView에 Pagination 무한 스크롤을 구현해보자

Madeline👩🏻‍💻·2024년 6월 28일
1

iOS study

목록 보기
56/61
post-custom-banner

오늘은 api 호출과 함께 Pagination하는 방법에 대해 알아보겠습니다!
UITableView를 구현해서, api 호출 결과를 띄우는데,
아래로 쭉 스크롤할 때, 스크롤 마지막에 가서 데이터를 추가!함으로써 무한으로 스크롤할 수 있게 말입죠

Pagination이 뭐야

페이지네이션은 데이터를 나누어 로드해서 유저에게 한 번에 너무 많은 데이터를 쏟아내지 않고, 필요한 만큼만 보여주는 기법입니다.
→ 따라서 Pagination은 대량의 데이터를 효율적으로 로드하고, UX를 향상시키기 위해 필수적입니다.

📍 1. 오프셋 기반 페이지네이션

오프셋 기반 페이지네이션은 데이터를 특정 위치(offset)부터 일정 개수만큼 가져오는 방식입니다.

예를 들어, 데이터 100개가 있을 때, 한 번에 20개씩 가져오려면,

  • 첫번째 페이지: offset = 0, limit = 20 (0번 데이터부터 20개)
  • 두번째 페이지: offset = 20, limit = 20 (20번 데이터부터 20개)
  • 세번째 페이지: offset = 40, limit = 20 (40번 데이터부터 20개)

왜 써??

오프셋 기반 페이지네이션은, 비교적으로 구현이 간단합니다.
데이터가 일정한 순서대로 정렬되어 있고, 데이터의 양이 적을 때 유용하구요!

근데 데이터의 양이 많아지면, 높은 오프셋 값으로 인해 쿼리 성능이 저하될 수 있습니다.

🖱️ 2. 커서 기반 페이지네이션

커서 기반 페이지네이션은 현재 페이지의 마지막 항목에 대한 참조(커서)를 사용하여, 다음 페이지의 데이터를 가져오는 방식입니다.

→ 각 요청 시 서버는 다음 페이지를 가져오기 위한 커서 반환

  • 첫번째 페이지: cursor = nil (처음부터 가져옴)
  • 두번째 페이지: cursor = abc123 (첫번째 페이지의 마지막 항목 참조)
  • 세번째 페이지: cursor = def456 (두번째 페이지의 마지막 항목 참조)

왜 써?

대량의 데이터가 있을 때, 성능이 뛰어납니다.
특히 데이터가 자주 변경되는 환경에서 유용합니다.

오프셋 기반 방식과 달리, 새로운 데이터가 추가되어도 페이지가 영향을 받지 않습니다. 따라서 더 안정적이고, 일관적입니당

func fetchData(cursor: String?) {
    var urlString = "https://api.example.com/data"
    if let cursor = cursor {
        urlString += "?cursor=\(cursor)"
    }
    let url = URL(string: urlString)!
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data else { return }
        
        // JSON 파싱 및 데이터 추가 처리
        let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
        let newCursor = json?["next_cursor"] as? String
        
        DispatchQueue.main.async {
            // 테이블 뷰 업데이트
            self.currentCursor = newCursor
        }
    }.resume()
}

// 데이터 요청 예제
var currentCursor: String?

func loadMoreData() {
    fetchData(cursor: currentCursor)
}

🛩️ 3. UITableView의 Prefetching 프로토콜

예를 들어, UITableView에 이미지가 많으면 빠르게 스크롤할 때 모든 이미지를 다 받아오느라 앱이 느려질 수 있습니다. 이를 해결하기 위해 iOS 10부터 제공하는 Prefetching 프로토콜을 사용할 수 있습니다.

Prefetching 프로토콜이란?
Prefetching 프로토콜은 TableView가 화면에 표시될 셀을 미리 준비할 수 있도록 도와줍니다. 이를 통해 스크롤 성능이 향상되고, 사용자가 더 부드럽게 스크롤할 수 있습니다. 이 방법은 페이지네이션이라기보다는 사용자 경험을 향상시키기 위한 최적화 방법입니다.

어떻게 동작하나?

  • 미리 로딩: TableView는 사용자가 스크롤을 시작하기 전에 곧 표시될 셀의 데이터를 미리 요청합니다. 즉, 사용자가 그 셀을 보기도 전에 데이터를 준비해 놓는 것이죠!!! 언제 요청할지에 대한 기준만 정해주면 됩니다!

  • 중간 취소: 사용자가 아주 빠르게 스크롤하면, TableView는 이미지를 다 받아오는 대신, 필요 없는 부분은 요청을 취소합니다. 이렇게 하면 불필요한 네트워크 요청을 줄이고, 중요한 데이터에 집중할 수 있습니다

이거랑 페이지네이션이랑 뭐가 다른가?
페이지네이션은 데이터를 일정한 크기로 나누어 단계적으로 로드하는 방식이다.
Prefetching은 스크롤할 때 미리 데이터를 로드해서 스크롤이 끊기지 않게 하는 방식이다.

😴 그러니깐, !!
페이지네이션은 대량의 데이터를 단계적으로 가져와서, 클라이언트와 서버 모두의 부담을 줄여준다. 데이터베이스에서 대량의 데이터를 다루는 경우, 한 번에 모든 데이터를 로드하기 부담스러운 경우에 사용하기 좋다.

테이블뷰의 Prefetching은 스크롤 UX를 향상시키기 위함이다. 이미지나 영상처럼 로딩 시간이 많이 필요한 데이터가 많은 리스트나, 스크롤 성능을 최적화하고, 끊김 없는 UX를 주고 싶을 때 사용한다.

결론

페이지네이션과 Prefetching은 그 목적과 장점이 다르기 때문에, 선택해서 사용하거나 둘 다 써도 좋겠다!!

예를 들어서, 쇼핑 앱에서 수백 개, 수천 개의 상품을 표시해야 하는 상황에서는

1) 페이지네이션: 서버에서 데이터를 페이지 단위로 요청(한 번에 20개만 요청 + 스크롤 할 때 추가 데이터 요청)
2) Prefetching: 사용자가 빠르게 스크롤할 때에도 부드럽게 스크롤할 수 있도록, 이미지 미리 로드 (15번째 상품 보고 있을 때, 16~20번째 이미지 미리 로드)

만들어보자

API

사용할 API에 대한 정보는 다음과 같습니다.

https://www.tvmaze.com/api

위 API에서 People 값 중 이미지랑 이름 정도만 받아와서, UITableView에 넣어보겠습니다.

저는 TableView의 Prefetching 프로토콜과 오프셋 기반의 페이지네이션을 사용해서 구현해볼겁니다.

  • 페이지네이션: 뷰모델's 데이터 가져오는 로직 코드
    Rx로 비동기적으로 네트워크 요청 +
    currentPage를 기준으로 서버에서 데이터를 가져오고, 성공적으로 데이터를 가져오면 currentPage를 ++ -> 다음 페이지의 데이터를 준비합니다.

가져온 데이터는 peopleSubject에 추가하고, currentPage를 증가시켜 다음 페이지!

private var peopleSubject = BehaviorSubject<[People]>(value: [])
//--------
private func fetchMorePeople() {
    fetchTVPeople(page: self.currentPage, group: nil) { result in
        switch result {
        case .success(let success):
            self.peopleList.append(contentsOf: success)
            self.currentPage += 1
            self.peopleSubject.onNext(self.peopleList)
        case .failure(let failure):
            self.peopleSubject.onError(failure)
        }
        self.isLoading = false
    }
}
  • Prefetching
    사용자가 스크롤할 때 미리 데이터를 로드하여, 스크롤 성능을 최적화합니다. 특정 지점에 도달하면 viewModel.apiRequest()를 호출하여 추가 데이터를 요청!
    indexPath.row의 배열에서 가장 큰 행이 지금 스크롤해서 내린(로드 된) 데이터 - 10보다 크면 (==유저가 데이터를 거의 다 봤따!!의 기준) 추가 데이터 요청
extension TVViewController: UITableViewDataSourcePrefetching {
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        guard let maxRow = indexPaths.map({ $0.row }).max() else { return }
        if maxRow > people.count - 10 {
            viewModel.apiRequest()
        }
    }
}
profile
🍎 Apple Developer Academy@POSTECH 2기, 🍀 SeSAC iOS 4기
post-custom-banner

0개의 댓글