Рассмотрите это как возможное решение.
public class Pageable<T> {
public enum ObjectState {
case loading
case loaded
}
public private (set) var page: Int = 0
private var items: [T] = [T]()
private var state: ObjectState = .loading
private let itemsPerPage: Int
private var itemsReloaded: (() -> ())
public init(itemsPerPage: Int, items: [T] = [], itemsReloaded: @escaping (() -> ())) {
self.items = items
self.itemsPerPage = itemsPerPage
self.itemsReloaded = itemsReloaded
}
public var itemsCount: Int {
switch state {
case .loaded:
return items.count
case .loading:
return items.count + 1 // should be displaying cell with loading indicator
}
}
public var isLoaded: Bool {
return state == .loaded
}
public var isLoading: Bool {
return state == .loading
}
public func append(contentsOf items: [T]) {
state = items.count < itemsPerPage ? .loaded : .loading
self.items.append(contentsOf: items)
itemsReloaded()
}
public func incrementPage() {
page += 1
}
public func reset() {
page = 0
state = .loading
items = []
}
public func itemFor(_ index: Int) -> T? {
return items.indices.contains(index) ? items[index] : nil
}
}
struct Property {}
protocol SearchItemsDisplayLogic: class {
func reloadItemsViews()
}
protocol SearchItemsInteraction {
func loadMore(page: Int)
}
// MARK: View Related with UITableView example
lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(pullToRefresh(_:)), for: UIControl.Event.valueChanged)
return refreshControl
}()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter.itemsCount
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
presenter.viewWillDisplayCellAt(indexPath.row)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if presenter.isLoadingCellNeeded(indexPath.row) {
return tableView.dequeueReusableCell(withIdentifier: "\(LoadingTableViewCell.self)", for: indexPath)
}
let cell = tableView.dequeueReusableCell(withIdentifier: "\(PropertyTableViewCell.self)", for: indexPath) as? PropertyTableViewCell
presenter.populate(cell: cell, indexPath: indexPath)
return cell ?? UITableViewCell(style: .default, reuseIdentifier: "\(UITableViewCell.self)")
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let property = presenter.property(indexPath.row) else {
return
}
}
protocol SearchItemsPresentation {
// MARK: Pagination logic
var itemsCount: Int { get }
// From the view.
func isLoadingCellNeeded(_ item: Int) -> Bool
func viewWillDisplayCellAt(_ item: Int)
func pullToRefresh()
func property(_ item: Int) -> Property?
// From the interactor.
func presentItems(items: [Property])
}
// MARK: - Presenter
class SearchItemsPresenter: SearchItemsPresentation {
weak var propertyDisplay: SearchItemsDisplayLogic?
lazy var interactor: SearchItemsInteraction? = {
return SearchItemsInteractor(presenter: self)
}()
var itemsCount: Int {
return pageable.itemsCount
}
private var pageable: Pageable<Property>!
init(viewController: SearchItemsDisplayLogic) {
self.propertyDisplay = viewController
pageable = Pageable(itemsPerPage: 15, itemsReloaded: {
self.propertyDisplay?.reloadItemsViews()
})
}
// TODO: presenter should not have UIKit!
func populate(cell: CellProtocol?, indexPath: IndexPath) {
guard let cell = cell else { return }
// populate
}
}
extension SearchItemsPresenter {
func property(_ index: Int) -> Property? {
return pageable.itemFor(index)
}
}
// MARK: Pageable
extension SearchItemsPresenter {
/// if it's loading show loading cell in the view.
func isLoadingCellNeeded(_ item: Int) -> Bool {
let isViewAtTheBottom = item == itemsCount - 1
return isViewAtTheBottom && pageable.isLoading
}
/// Called in `willDisplay` methods of the view.
func viewWillDisplayCellAt(_ item: Int) {
let isViewAtTheBottom = item == itemsCount - 1
if isViewAtTheBottom && pageable.isLoading {
interactor?.loadMore(page: pageable.page)
pageable.incrementPage()
}
}
func pullToRefresh() {
pageable.reset()
interactor?.loadMore(page: pageable.page)
pageable.incrementPage()
}
func presentItems(items: [Property]) {
pageable.append(contentsOf: items)
}
}
// MARK: - Interactor
class SearchItemsInteractor: SearchItemsInteraction {
private var presenter: SearchItemsPresentation
init(presenter: SearchItemsPresentation) {
self.presenter = presenter
}
func loadMore(page: Int) {
DispatchQueue.global(qos: .background).async {
sleep(1)
DispatchQueue.main.async {
// TODO: return some data
self.presenter.presentItems(items: [])
}
}
}
}