데이터를 fetch하는 함수를 만들때
현재 page를 매개변수로 받는다
데이터 fetch를 다 했다면 completionHandler
의 결과값을 매개변수로 전달할텐데
이때 전달받은 현재 page를 +1 해서 매개변수로 돌려준다
이를 받아 현재 page 값을 update하고
delegate
메소드 중 willDisplay
에서
현재 page가 1일 때를 제외하고
이제 보여질 cell의 indexPath값에서 (indexPath.row + 1) / 25 + 1
가 currentPage와 같다면 다시 데이터를 fetch한 후 breweryList에 추가해주면된다
왜 나누기 25냐면 PunkAPI가 기본적으로 25개씩 불러오기 때문! (이 개수는 쿼리로 설정할 수 있음)
- ex) 처음 앱이 실행되었을 때 `currentPage`는 2 (한번 데이터를 fetch 하면 +1 되니까)
스크롤 하다가 24번째(25개씩 데이터를 받아왔으니까 24번째가 마지막이다)가 보여질때
(24 + 1) / 25 + 1 = 2 이므로 `currentPage`와 값이 같으므로
다시 데이터를 fetch한다.
UITableViewDataSourcePrefetching
의 메소드중 prefetchRowsAt
를 사용하여 구현할 수도 있다고 하던데 나중에 구현해봐야겠다.import UIKit
import SnapKit
class InfiniteScrollViewController: UIViewController {
private let fetchData = FetchData()
private var breweryList = [Brewery]()
private var currentPage = 1
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
fetchData.fetch(page: currentPage) { [weak self] breweryList, updatedPage in
guard let self = self else { return }
self.breweryList = breweryList
self.currentPage = updatedPage
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
extension InfiniteScrollViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard currentPage != 1 else { return }
if (indexPath.row + 1) / 25 + 1 == currentPage {
fetchData.fetch(page: currentPage) { [weak self] breweryList, updatedPage in
guard let self = self else { return }
self.breweryList.append(contentsOf: breweryList)
self.currentPage = updatedPage
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
extension InfiniteScrollViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return breweryList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "\(breweryList[indexPath.row].id)_" + breweryList[indexPath.row].name
return cell
}
}
private extension InfiniteScrollViewController {
func setupLayout() {
view.addSubview(tableView)
tableView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
}
// Model
struct Brewery: Decodable {
let id: Int // 무한 스크롤 확인용
let name: String
}
// FetchData
struct FetchData {
func fetch(page: Int, completionHandler: @escaping ([Brewery], Int) -> Void) {
guard var component = URLComponents(string: "https://api.punkapi.com/v2/beers") else { return }
let queryItem = URLQueryItem(name: "page", value: "\(page)")
component.queryItems = [queryItem]
guard let url = component.url else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
guard let response = response as? HTTPURLResponse else { return }
switch response.statusCode {
case (200..<300):
guard let data = data else { return }
do {
let result = try JSONDecoder().decode([Brewery].self, from: data)
completionHandler(result, page+1)
} catch {
print("do-catch { \(error.localizedDescription) }")
}
default:
guard let error = error else { return }
print("statusCode error { \(error.localizedDescription) }")
}
}
.resume()
}
}