Search code examples
iosswiftmvvmrx-swift

RxSwift pagination tableView near bottom


So i am doing paging in my app yet i encounter some strange behavior :

class ViewController: UIViewController {
   func bindRx() {
    btn.rx
        .tap
        .bind(to: viewModel.searchButtonTapped)
        .disposed(by: disposeBag)
    
    searchField.rx
        .text
        .orEmpty
        .bind(to: viewModel.searchText)
        .disposed(by: disposeBag)
    
    searchField.rx
        .searchButtonClicked
        .bind(to: viewModel.searchButtonTapped)
        .disposed(by: disposeBag)
    
    viewModel.itemsDriver.drive(tableView.rx.items) { (tableView, row, element) -> UITableViewCell in
        
        switch element {
        case .post(model: let vm):
            let cell = tableView.dequeueReusableCell(withIdentifier: "PostTableViewCell") as! PostTableViewCell
            cell.config(viewModel: vm)
            return cell
            
        case .promotion(title: _):
            let cell = tableView.dequeueReusableCell(withIdentifier: "PromotionTableViewCell") as! PromotionTableViewCell
            return cell
        }
        
    }.disposed(by: disposeBag)
    
    viewModel
        .isLoading
        .drive(tableView.rx.animation)
        .disposed(by: disposeBag)
    
    tableView.rx
        .modelSelected(CellType.self)
        .bind(to: viewModel.selectedCell)
        .disposed(by: disposeBag)
    
    
 //        tableView.rx
  //            .willDisplayCell
  //            .subscribe(onNext: { cell, indexPath in
 //                let lastItem = self.viewModel.counter - 1
  //                if indexPath.row == lastItem {
 //                    self.viewModel.loadMore.accept(())
  //                }
 //            })
 //            .disposed(by: disposeBag)
    
    tableView.rx
        .reachedBottom()
        .debug("botoom!!!!!!!!!")
        .bind(to: self.viewModel.loadMore)
        .disposed(by: disposeBag)
}

}

public extension Reactive where Base: UIScrollView {
/**
 Shows if the bottom of the UIScrollView is reached.
 - parameter offset: A threshhold indicating the bottom of the UIScrollView.
 - returns: ControlEvent that emits when the bottom of the base UIScrollView is reached.
 */
func reachedBottom(offset: CGFloat = 0.0) -> ControlEvent<Void> {
    let source = contentOffset.map { contentOffset in
        let visibleHeight = self.base.frame.height - self.base.contentInset.top - self.base.contentInset.bottom
        let y = contentOffset.y + self.base.contentInset.top
        let threshold = max(offset, self.base.contentSize.height - visibleHeight)
        return y >= threshold
    }
    .distinctUntilChanged()
    .filter { $0 }
    .map { _ in () }
    return ControlEvent(events: source)
}

}

final class PostsViewModel {

init(networking: Networking = Networking()) {
    self.networking = networking
    
    
    
    searchButtonTapped.subscribe(onNext: { [weak self] value in
        self?.page = 0
        self?.counter = 0
        self?.loadMore.accept(())
    }).disposed(by: disposeBag)
    
    searchText.subscribe(onNext: { [weak self] value in
        self?.params.updateValue(value, forKey: "search_query")
    }).disposed(by: disposeBag)
    
    
    let load = loadMore.scan(0) { (value, _) -> Int in
        self.page = self.page + 1
        self.params.updateValue(self.page, forKey: "page")
        return value + 1
    }
            
   let test = load
        .flatMapLatest { page in
            self.networking.preformNetworkTaskGet(
                endPoint: Api.get,
                type: Posts.self,
                methodType: .get,
                param: self.params)
              //  .debug("call1 🚘")
        }
    .scan(into: [Post]()) { current, items in
        current.append(contentsOf: items.data)
        self.counter = current.count
        }
  //  .debug(" all  🚘")
    .share()
    
    let vm = test.map {
        self.postsToCellViewModel(posts: $0)
    }
    
   
    
    itemsDriver = vm
        .map { $0 }
        .asDriver(onErrorJustReturn: [])
        .debug("🚘2")
}

 struct Networking: NetworkType {
func downloadImage(url: String) -> Observable<UIImage> {
    return Observable<UIImage>.create { (observer) -> Disposable in
            AF.request(url).response{ response in
                print(response)
                switch response.result {
                case .success(let responseData):
                    observer.onNext(UIImage(data: responseData!)!)
                case .failure(let error):
                    observer.onError(error)
                }
            }
        return Disposables.create()
    }
}


func preformNetworkTaskGet<T: Codable>(endPoint: EndpointType, type: T.Type, methodType: MethodsType, param: [String : Any]?) -> Observable<T> {
    return Observable<T>.create { (observer) -> Disposable in
        if let url = endPoint.baseURL.appendingPathComponent(endPoint.path).absoluteString.removingPercentEncoding {
            AF.request(url, method: methodType.method, parameters: param, encoding: URLEncoding.default).responseJSON  { (response) in
                switch response.result {
                case .failure(let error):
                    observer.onError(error)
                case .success(_):
                    if let data = response.data {
                        let response = Response.init(data: data)
                        if let decode = response.decode(type) {
                            observer.onNext(decode)
                        } else {
                            observer.onError(NSError())
                        }
                    }
                }
            }
        }
        return Disposables.create()
    }
}

}

After the first network call i will enter the if and trigger self.viewModel.loadMore.accept(()) without any scrolling. Any ideas?


Solution

  • You can simply skip the first next event of the reachedBottom Observable...

    tableView.rx.reachedBottom().skip(1)