ReactiveX / RxSwift

Reactive Programming in Swift
MIT License
24.36k stars 4.16k forks source link

TableView RxSwiftified way to detect Scroll reached at the bottom #926

Closed vjujjavarapu-zz closed 8 years ago

vjujjavarapu-zz commented 8 years ago

Could you please suggest an rxswifified way to detect tableview reached the bottom, I have seen the Rx Repo and i could not clearly get it. I know the regular way of scrollviewdelegate and setting the delegate to trigger scrollviewdidscroll, However now i am using RxAlamofire and i get an assertion error delegate has already been set if i do it in the conventional way.

AndrewSB commented 8 years ago

Hey @vjujjavarapu!

I think the Github repository search example does a great job of detecting when a tableView reaches the bottom (bottom - 20pts in the example's case) of its content.

Check out https://github.com/ReactiveX/RxSwift/blob/master/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift#L45, and the entire example for that matter, and come back with any questions you have 👍

vjujjavarapu-zz commented 8 years ago
static let startLoadingOffset: CGFloat = 20.0
  static func isNearTheBottomEdge(contentOffset: CGPoint, _ tableView: UITableView) -> Bool {
        return contentOffset.y + tableView.frame.size.height + startLoadingOffset > tableView.contentSize.height
    }
 let loadNextPage = tableView.rx_contentOffset
            .flatMap { offset in
                ViewController.isNearTheBottomEdge(offset, tableView)
                    ? Observable.just()
                    : Observable.empty()
        }

This method is not called at all even after doing

tableView.rx_setDelegate(self)
            .addDisposableTo(disposeBag)
kzaher commented 8 years ago

Hi @vjujjavarapu ,

you need to call one of subscribe family of methods. You have just created sequence definition.

vjujjavarapu-zz commented 8 years ago

i just followed your example GitHubSearchRepositories can you please specify which subscribe are you talking about in this file.

class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegate {
    static let startLoadingOffset: CGFloat = 20.0

    static func isNearTheBottomEdge(contentOffset: CGPoint, _ tableView: UITableView) -> Bool {
        return contentOffset.y + tableView.frame.size.height + startLoadingOffset > tableView.contentSize.height
    }

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchBar: UISearchBar!

    let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Repository>>()

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = self.tableView
        let searchBar = self.searchBar

        dataSource.configureCell = { (_, tv, ip, repository: Repository) in
            let cell = tv.dequeueReusableCellWithIdentifier("Cell")!
            cell.textLabel?.text = repository.name
            cell.detailTextLabel?.text = repository.url
            return cell
        }

        dataSource.titleForHeaderInSection = { dataSource, sectionIndex in
            let section = dataSource.sectionAtIndex(sectionIndex)
            return section.items.count > 0 ? "Repositories (\(section.items.count))" : "No repositories found"
        }

        let loadNextPageTrigger = tableView.rx_contentOffset
            .flatMap { offset in
                GitHubSearchRepositoriesViewController.isNearTheBottomEdge(offset, tableView)
                    ? Observable.just()
                    : Observable.empty()
            }

        let searchResult = searchBar.rx_text.asDriver()
            .throttle(0.3)
            .distinctUntilChanged()
            .flatMapLatest { query -> Driver<RepositoriesState> in
                if query.isEmpty {
                    return Driver.just(RepositoriesState.empty)
                } else {
                    return GitHubSearchRepositoriesAPI.sharedAPI.search(query, loadNextPageTrigger: loadNextPageTrigger)
                        .asDriver(onErrorJustReturn: RepositoriesState.empty)
                }
            }

        searchResult
            .map { $0.serviceState }
            .drive(navigationController!.rx_serviceState)
            .addDisposableTo(disposeBag)

        searchResult
            .map { [SectionModel(model: "Repositories", items: $0.repositories)] }
            .drive(tableView.rx_itemsWithDataSource(dataSource))
            .addDisposableTo(disposeBag)

        searchResult
            .filter { $0.limitExceeded }
            .driveNext { n in
                showAlert("Exceeded limit of 10 non authenticated requests per minute for GitHub API. Please wait a minute. :(\nhttps://developer.github.com/v3/#rate-limiting") 
            }
            .addDisposableTo(disposeBag)

        // dismiss keyboard on scroll
        tableView.rx_contentOffset
            .subscribe { _ in
                if searchBar.isFirstResponder() {
                    _ = searchBar.resignFirstResponder()
                }
            }
            .addDisposableTo(disposeBag)

        // so normal delegate customization can also be used
        tableView.rx_setDelegate(self)
            .addDisposableTo(disposeBag)

        // activity indicator in status bar
        // {
        GitHubSearchRepositoriesAPI.sharedAPI.activityIndicator
            .drive(UIApplication.sharedApplication().rx_networkActivityIndicatorVisible)
            .addDisposableTo(disposeBag)
        // }
    }

    // MARK: Table view delegate

    func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 30
    }

    deist {
        // I know, I know, this isn't a good place of truth, but it's no
        self.navigationController?.navigationBar.backgroundColor = nil
    }
}
vjujjavarapu-zz commented 8 years ago

I just want to know which part of this triggers and modifies or changes the loadNextPageTrigger! Thank you!

AndrewSB commented 8 years ago

Try doing something like

loadNextPage.subscribe(onNext: { print("the table view is near the bottom!") })
vjujjavarapu-zz commented 8 years ago

@AndrewSB Thank you for your reply. However can you suggest a way that this prints only once. Or Rather i want to subscribe only once. This one is printing "the tableview is near the bottom!" atleast 10 times. I just want to print it only once .

AndrewSB commented 8 years ago

that's because you're subscribing to the tableView's contentOffset. You can try something like .take(1), .distinctUntilChanged, or .debounce to only fire the event once 😄

vjujjavarapu-zz commented 8 years ago

Thank you :-)

On Sat, Oct 8, 2016 at 7:27 PM Andrew Breckenridge notifications@github.com wrote:

that's because you're subscribing to the tableView's contentOffset. You can try something like .distinctUntilChanged, or .debounce to only fire the event once 😄

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ReactiveX/RxSwift/issues/926#issuecomment-252454152, or mute the thread https://github.com/notifications/unsubscribe-auth/AJy_-Ef25W25sFh6crM8zldQhYdaxSxcks5qyCbngaJpZM4KPKg0 .

vjujjavarapu-zz commented 8 years ago

However i did not see loadNextPageTrigger Subscribe / take/ distinctUntilChanged or .Debounce in the GitHubSearchRepositories example in the RxExamples in the current repo.

AndrewSB commented 8 years ago

That's because in the example, its fine if the load next page triggers more than once 🙃

vjujjavarapu-zz commented 8 years ago

Thank you @AndrewSB. You saved my life.

AndrewSB commented 8 years ago

🤗

SamiYousef commented 5 years ago

RxSwift pagination duplicate request duplicate pagination request:

-just remove searchBar from RXExample https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/GitHubSearchRepositories

-when scroll to bottom "performSearch" called twice after each other.

Expected outcome:

when scroll to bottom call "performSearch" only once https://stackoverflow.com/questions/53615310/rxswift-pagination-duplicate-request