iOS

iOS ) Firestore 와 UITableView를 활용한 Paging 기능

JoonSwift 2022. 3. 7. 16:18

최근 Firestore를 활용하면서 Paging기능을 구현할 일이 생겨 블로그 포스팅으로 정리해보려고 합니다. 

 

우선, 첫째로 Firestore에서 데이터를 받아온 후 다음 Query를 저장.

둘째, Paging을 시작하는 시점.

셋째, 상태값 (isPaging, isLastPage) 관리.

저는 이렇게 3가지를 이번 기능을 구현하면서 중점적으로 생각했던것 같습니다. 

 

Firestore

Firebase 공식 문서에서는 쿼리 커서로 데이터 페이지화 라는 섹션에서 구현 방법을 확인할 수 있습니다. 

간단히 정리하면 start(), startAfter() 메서드와 limit() 메서드를 적절히 활용하여 구현할 수 있었습니다. 바로 코드로 확인해보겠습니다. 

 

Firestore Paging 구현

final class FirestoreApi {
  
  private let fireStore = Firestore.firestore()
  private var query: Query? = nil

우선 query를 가지고 있을 수 있는 query 프로퍼티를 정의하였습니다. 이후 FirestoreApi 내부에 fetchGreetings(completion:) 메서드를 정의합니다. 

  func fetchGreetings(completion: @escaping ([CellModel]) -> Void) {
    let collection = fireStore.collection("cells")
    let requestQuery: Query
    
    if let query = query {
      //There is last query
      requestQuery = query
    } else {
      //It's First query request
      requestQuery = collection
        .order(by: "number")
        .limit(to: 5)
    }

우선 아래의 조건문의 역할은 바로 query의 존재 유무입니다. 만약, 처음 query를 요청하는것이라면, 새로운 query를 생성하고, 만약 이미 query가 저장되어 있다면, 해당 query를 requestQuery에 저장합니다. 

즉, requestQuery는 해당 데이터를 처음 요청하는 것이라면, 1페이지에 대한 query를 담을 것이고, 그렇지 않다면 2페이지나 그 이후의 페이지에 대한 query를 담고 있을것입니다.

다음은 addSnapshotListener를 통해 snapshot을 가져오는 코드를 작성합니다.

  requestQuery.addSnapshotListener({ [weak self] (snapshot, error) in
      guard let self = self else { return }
      
      guard let snapshot = snapshot,
            let lastDocument = snapshot.documents.last else {
              completion([])
              return
            }
      
      let next = collection
        .order(by: "number")
        .limit(to: 5)
        .start(afterDocument: lastDocument)
      
      //Set next query
      self.query = next
      
      //가져온 document들 활용..

snapshot의 document들 중 제일 마지막을 lastDocument에 저장한 후, 새로운 Query를 만들고, 이를 next에 저장한 후 아까 class 선언부에서 보았던 query 프로퍼티에 저장시켜줍니다. 

이는 만약 1페이지를 가져왔었다면, next는 2페이지에 대한 Query가 저장됩니다. 

이로써 Firestore의 Paging기능은 구현했습니다. 이제 이 fetchGreetings 메서드를 호출할 때 마다, next는 다음 페이지의 Query를 저저장하여 query 프로퍼티에 넘겨줄 것입니다. 

Paging을 시작하는 시점

Paging을 시작하는 시점은 scrollViewDidScroll(_:) 메서드를 활용했습니다. 

  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let yOffset = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height
    let height = scrollView.frame.height
    
    if yOffset > (contentHeight - height) {
      if !isPaging && !isLastPage {
        //Paging!
        print("Paging!")
        activityIndicator.startAnimating()
        beginPaging()
      }
    }
  }

scrollView의 contentOffset.y 와 contentSize.height, 그리고 frame.height를 활용한 구현이며, 이후에 isPaging과 isLastPage에 대한 조건이 없다면, 정말 많은 request를 날리게 되니 주의해야합니다..

 

상태값 관리

우선 Paging이 시작하면, isPaging을 true로 설정해줍니다. 이후 Firestore에서 데이터를 가져오는 fetchGreetings() 메서드를 호출합니다.

  private func beginPaging() {
    isPaging = true
    
    DispatchQueue.main.async {
      self.fetchGreetings()
    }
  }

fetchGreetings() 메서드에서는, 기존에 Firestore Paging을 구현할 때, 설정해 준 limit() 메서드의 파라미터와 연관하여, 만약 가져온 데이터가 5개보다 작으면, isLastPage를 true로 설정해 줍니다. 그리고 paging이 끝났으니 isPaging 도 false로 변경해줍니다. 

  func fetchGreetings() {
    DispatchQueue.global(qos: .utility).async { [weak self] in
      self?.firestoreApi.fetchGreetings(completion: { cellModels in
        self?.activityIndicator.stopAnimating()
        if cellModels.count < 5 {
          self?.isLastPage = true
        }
        self?.isPaging = false
        self?.cellModels += cellModels
      })
    }
  }

 

실행 화면

 

소스코드

https://github.com/elddy0948/Play/tree/main/FirestorePagingExample

 

GitHub - elddy0948/Play: 플레이

플레이. Contribute to elddy0948/Play development by creating an account on GitHub.

github.com