본문 바로가기
iOS Swift/Study

[Swift] 책 검색 App (10) - 무한 스크롤

by 야고이 2024. 5. 11.
728x90

지금까지는 기본 값인 1페이지의 책들만 불러왔는데 이젠 전체 페이지에서 검색값을 불러올까 싶다

우선 어디서 다음 페이지를 불러올지를 생각해봐야한다

간단하게 생각하면 스크롤이 끝나는 부분에서 불러오면 될거 같은데 스크롤이 끝나기 조금 이전에 다음페이지를 불러오면 좋을 것 같다

테이블뷰 셀의 2-3개 높이 정도. 그러니까 스크롤이 250 픽셀 정도 남았을 때 다음 페이지를 불러오는 로직으로 짜보았다

아래의 식으로 남은 스크롤 높이를 구할 수 있다

남은 스크롤 높이 = 전체 콘텐츠 높이 - 뷰 상에서 보여지는 스크롤 높이 - 현재 스크롤 위치

 

 

전체 콘텐츠 높이

let contentsSizeHeight = scrollView.contentSize.height

 

 

뷰 상에서 보여지는 스크롤 높이

let scrollHeight = scrollView.bounds.size.height - scrollView.safeAreaInsets.bottom

스크롤뷰의 bottom 은 뷰의 superView 까지 잡혀있는데 탭뷰에 가려져서 그 부분 만큼 빼줘야한다

그래야 뷰 상에 보여지는 스크롤 뷰만큼의 높이를 구할 수 있다

 

 

현재 스크롤 위치

let scrollOffset = scrollView.contentOffset.y

스크롤 위치는 테이블뷰 가장 상단부터를 말한다

가장 상위에서 더 위로 당기면 마이너스 값이 찍힌다

 

 

남은 스크롤 높이

let scrollOffsetFromBottom = (contentsSizeHeight - scrollHeight) - scrollOffset

남은 스크롤 높이 = (전체 콘텐츠 높이 - 뷰 상에서 보여지는 스크롤 높이) - 현재 스크롤 위치

 

 

250 픽셀 정도 남았을 때 다음페이지 로드

if scrollOffsetFromBottom <= 250 {
    self.moreLoad()
}

여기에 다음페이지르 로드하는 함수를 넣어준다

 

 

이 코드들은 스크롤을 할 때 실행되어야 하므로 UIScrollDelegate 를 채택한 뒤 scrollViewDidScroll 에서 해줘야한다

func scrollViewDidScroll(_ scrollView: UIScrollView)

 

전체코드

 func scrollViewDidScroll(_ scrollView: UIScrollView) {     
    let scrollOffset = scrollView.contentOffset.y // 현재 위치
    let scrollHeight = scrollView.bounds.size.height - scrollView.safeAreaInsets.bottom //스크롤 가능 높이
    let contentsSizeHeight = scrollView.contentSize.height // 콘텐츠 전체 높이

    let scrollOffsetFromBottom = (contentsSizeHeight - scrollHeight) - scrollOffset
    if scrollOffsetFromBottom <= 250 {
        self.moreLoad()
    }
}

 


네트워킹 수정

 

기존의 네트워킹하는 함수의 파라미터에 page를 추가 해준다 기본 값은 1로 넣어준다

func fetchBookData(withQuery query: String, targets: [SearchBookTarget] = [], page: Int = 1, completion: @escaping (Bool, SearchBookResponse?) -> Void) {

 

 

쿼리에도 page 를 추가해준다

한 페이지에 보여지 문서수인 size 도 50으로 최대값으로 넣어줬다

var queryItems: [URLQueryItem] = [
        URLQueryItem(name: "query", value: query),
        URLQueryItem(name: "page", value: String(page)),
        URLQueryItem(name: "size", value: "50"),
    ]

 


페이지 로드 메서드 생성

검색시 넣은 키워드를 가져옵니다

기존의 코드에서 책을 검색하는 메서드(아래 더보기 참고) 에서만 keyword 를 알 수 있고 바깥에서는 알 수 없습니다

더보기
func searchBook(keyword: String) {
    print("searchBook \(keyword)")
    // api 통신
    BookManager.shared.fetchBookData(withQuery: keyword, targets: [.title, .person]) { [weak self] success, response in
        if success, let response = response {

            // 결과값 모델
            self?.searchBookDocuments = response.documents

            // 테이블뷰 리로드
            self?.tableView.reloadData()
        }
        else {
            // TODO: error UI

        }
    }
}

바깥에다가 검색키워드를 선언해 줍니다

var searchKeyword: String?

 

페이지도 기본값을 넣어서 선언해줍니다

var searchPage: Int = 1

 

 

스크롤 높이가 250 이하 일 때 페이지가 +1 이 됩니다

 

self.searchPage += 1

 

기존의 검색한 결과 배열에 다음 페이지 결과 값도 배열에 추가해 줍니다

self?.searchBookDocuments = searchBookDocuments + response.documents

 

 

 

전체 코드는 아래와 같습니다

책을 검색하는 메서드 와는 위의 두줄 외에 다를게 없습니다

func moreLoad() {
    guard let searchKeyword = self.searchKeyword else { return }
    self.searchPage += 1

    BookManager.shared.fetchBookData(withQuery: searchKeyword, targets: [.title, .person], page: self.searchPage) { [weak self] success, response in
        if success, let response = response, let searchBookDocuments = self?.searchBookDocuments {
//                print("response: \(response)")

            // 결과값 모델
            self?.searchBookDocuments = searchBookDocuments + response.documents

            // 테이블뷰 리로드
            self?.tableView.reloadData()
        }
        else {
            // TODO: error UI

        }
    }
}

 

 

근데 한가지 문제가 있습니다

남은 스크롤이 250 이하 일 '때 마다' 위 메서드가 호출 됩니다 

이렇게 되면 남은 스크롤이 200, 180, 50 일 때도 해당 페이지의 검색 결과들이 불러와 질거예요

저는 딱 한번만 다음페이지를 불러오고 싶습니다

 

검색 결과를 로딩중인 걸 Bool 값으로 판단해 볼게요

그리고 문서에 보면 마지막 페이지인지 여부를 판단하는 값을 제공해주니 그 값도 사용해보겠습니다

더보기
마지막 페이지 여부를 api 에서 제공해줌
var searchLoading: Bool = false // 검색결과 로딩 여부
var searchIsEnd: Bool = false // 다음 페이지 유무 여부

 

메서드를 아래와 같이  수정합니다

데이터를 로딩 중이면 moreLoad 메서드를 또 실행하지 않고 종료 합니다

책 검색 결과를 다 불러왔으면 데이터 로드 여부를 false 로 합니다

검색 결과의 마지막 페이지 여부를 확인하고 마지막 페이지라면 메서드를 종료 합니다

func moreLoad() {
    guard let searchKeyword = self.searchKeyword else { return }

    if self.searchLoading { return } // 데이터 로딩중(true) 이면 메서드 종료
    if self.searchIsEnd { return } // 마지막 페이지(true) 이면 메서드 종료

    self.searchPage += 1 // 페이지 증가
    self.searchLoading = true // 다음 페이지 로드 시작
    print("moreLoad \(self.searchPage)")

    BookManager.shared.fetchBookData(withQuery: searchKeyword, targets: [.title, .person], page: self.searchPage) { [weak self] success, response in
        if success, let response = response, let searchBookDocuments = self?.searchBookDocuments {
//                print("response: \(response)")
            print("response.meta.isEnd: \(response.meta.isEnd)")

            self?.searchIsEnd = response.meta.isEnd // 검색 결과 페이지가 완료 되었는지 확인

            // 결과값 모델
            self?.searchBookDocuments = searchBookDocuments + response.documents

            // 테이블뷰 리로드
            self?.tableView.reloadData()
        }
        else {
            // TODO: error UI

        }
        self?.searchLoading = false // 로드 완료됨
        print("moreLoad completed \(self?.searchPage)")
    }
}

 

 

책검색 메서드 수정

추가 페이지 로드하는 메서드와 중복 코드가 많으므로 아래와 같이 수정하였습니다

페이지, 검색키워드, 마지막페이지여부, 결과 배열을 모두 초기화 시키고 데이터를 로드합니다

func searchBook(keyword: String) {
    print("searchBook \(keyword)")

    // 초기화
    self.searchPage = 0
    self.searchKeyword = keyword
    self.searchIsEnd = false
    self.searchBookDocuments = []

    moreLoad()
}

 

참고.

마지막 페이지 여부 is_end 값을 넣지 않으면 마지막 페이지 뒤에 첫번째 페이지가 다시 로드 되어서 정말 무한 스크롤이 됩니다.

끝도 없이 내려간다,,

 

728x90

댓글